Marwan Alwali 2780a2dc7c update
2025-09-16 15:10:57 +03:00

1227 lines
42 KiB
Plaintext

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.http import JsonResponse, HttpResponse
from django.core.paginator import Paginator
from django.db.models import Q, Count, Sum, Avg
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from django.contrib.auth.models import User
from datetime import timedelta
import json
from .models import (
BloodGroup, Donor, BloodComponent, BloodUnit, BloodTest, CrossMatch,
BloodRequest, BloodIssue, Transfusion, AdverseReaction, InventoryLocation,
QualityControl
)
from .forms import (
DonorForm, BloodUnitForm, BloodTestForm, CrossMatchForm, BloodRequestForm,
BloodIssueForm, TransfusionForm, AdverseReactionForm, InventoryLocationForm,
QualityControlForm, DonorEligibilityForm, BloodInventorySearchForm
)
@login_required
def dashboard(request):
"""Blood bank dashboard with key metrics"""
context = {
'total_donors': Donor.objects.filter(status='active').count(),
'total_units': BloodUnit.objects.filter(status='available').count(),
'pending_requests': BloodRequest.objects.filter(status='pending').count(),
'expiring_soon': BloodUnit.objects.filter(
expiry_date__lte=timezone.now() + timedelta(days=7),
status='available'
).count(),
'recent_donations': BloodUnit.objects.filter(
collection_date__gte=timezone.now() - timedelta(days=7)
).count(),
'active_transfusions': Transfusion.objects.filter(
status__in=['started', 'in_progress']
).count(),
}
# Blood group distribution
blood_group_stats = BloodUnit.objects.filter(status='available').values(
'blood_group__abo_type', 'blood_group__rh_factor'
).annotate(count=Count('id'))
context['blood_group_stats'] = blood_group_stats
# Recent activities
context['recent_units'] = BloodUnit.objects.select_related(
'donor', 'component', 'blood_group'
).order_by('-collection_date')[:10]
context['urgent_requests'] = BloodRequest.objects.filter(
urgency='emergency', status__in=['pending', 'processing']
).select_related('patient', 'component_requested')[:5]
return render(request, 'blood_bank/dashboard.html', context)
# Donor Management Views
@login_required
def donor_list(request):
"""List all donors with filtering and search"""
donors = Donor.objects.select_related('blood_group').order_by('-registration_date')
# Search functionality
search_query = request.GET.get('search')
if search_query:
donors = donors.filter(
Q(donor_id__icontains=search_query) |
Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) |
Q(phone__icontains=search_query)
)
# Filter by status
status_filter = request.GET.get('status')
if status_filter:
donors = donors.filter(status=status_filter)
# Filter by blood group
blood_group_filter = request.GET.get('blood_group')
if blood_group_filter:
donors = donors.filter(blood_group_id=blood_group_filter)
paginator = Paginator(donors, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
'blood_groups': BloodGroup.objects.all(),
'status_choices': Donor.STATUS_CHOICES,
'search_query': search_query,
'status_filter': status_filter,
'blood_group_filter': blood_group_filter,
}
return render(request, 'blood_bank/donor_list.html', context)
@login_required
def donor_detail(request, donor_id):
"""Donor detail view with donation history"""
donor = get_object_or_404(Donor, id=donor_id)
# Get donation history
blood_units = BloodUnit.objects.filter(donor=donor).select_related(
'component', 'blood_group'
).order_by('-collection_date')
context = {
'donor': donor,
'blood_units': blood_units,
'total_donations': blood_units.count(),
'last_donation': blood_units.first(),
}
return render(request, 'blood_bank/donor_detail.html', context)
@login_required
@permission_required('blood_bank.add_donor')
def donor_create(request):
"""Create new donor"""
if request.method == 'POST':
form = DonorForm(request.POST)
if form.is_valid():
donor = form.save(commit=False)
donor.created_by = request.user
donor.save()
messages.success(request, f'Donor {donor.donor_id} created successfully.')
return redirect('blood_bank:donor_detail', donor_id=donor.id)
else:
form = DonorForm()
return render(request, 'blood_bank/donor_form.html', {'form': form, 'title': 'Add New Donor'})
@login_required
@permission_required('blood_bank.change_donor')
def donor_update(request, donor_id):
"""Update donor information"""
donor = get_object_or_404(Donor, id=donor_id)
if request.method == 'POST':
form = DonorForm(request.POST, instance=donor)
if form.is_valid():
form.save()
messages.success(request, f'Donor {donor.donor_id} updated successfully.')
return redirect('blood_bank:donor_detail', donor_id=donor.id)
else:
form = DonorForm(instance=donor)
return render(request, 'blood_bank/donor_form.html', {
'form': form, 'donor': donor, 'title': 'Update Donor'
})
@login_required
def donor_eligibility_check(request, donor_id):
"""Check donor eligibility for donation"""
donor = get_object_or_404(Donor, id=donor_id)
if request.method == 'POST':
form = DonorEligibilityForm(request.POST)
if form.is_valid():
# Process eligibility check
messages.success(request, f'Donor {donor.donor_id} is eligible for donation.')
return redirect('blood_bank:blood_unit_create', donor_id=donor.id)
else:
form = DonorEligibilityForm()
context = {
'donor': donor,
'form': form,
'is_eligible': donor.is_eligible_for_donation,
'next_eligible_date': donor.next_eligible_date,
}
return render(request, 'blood_bank/donor_eligibility.html', context)
# Blood Unit Management Views
@login_required
def blood_unit_list(request):
"""List all blood units with filtering"""
form = BloodInventorySearchForm(request.GET)
blood_units = BloodUnit.objects.select_related(
'donor', 'component', 'blood_group'
).order_by('-collection_date')
if form.is_valid():
if form.cleaned_data['blood_group']:
blood_units = blood_units.filter(blood_group=form.cleaned_data['blood_group'])
if form.cleaned_data['component']:
blood_units = blood_units.filter(component=form.cleaned_data['component'])
if form.cleaned_data['status']:
blood_units = blood_units.filter(status=form.cleaned_data['status'])
if form.cleaned_data['expiry_days']:
expiry_date = timezone.now() + timedelta(days=form.cleaned_data['expiry_days'])
blood_units = blood_units.filter(expiry_date__lte=expiry_date)
paginator = Paginator(blood_units, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
'form': form,
}
return render(request, 'blood_bank/blood_unit_list.html', context)
@login_required
def blood_unit_detail(request, unit_id):
"""Blood unit detail view with test results"""
blood_unit = get_object_or_404(BloodUnit, id=unit_id)
# Get test results
tests = BloodTest.objects.filter(blood_unit=blood_unit).select_related('tested_by')
crossmatches = CrossMatch.objects.filter(blood_unit=blood_unit).select_related(
'recipient', 'tested_by'
)
context = {
'blood_unit': blood_unit,
'tests': tests,
'crossmatches': crossmatches,
}
return render(request, 'blood_bank/blood_unit_detail.html', context)
@login_required
@permission_required('blood_bank.add_bloodunit')
def blood_unit_create(request, donor_id=None):
"""Create new blood unit"""
donor = None
if donor_id:
donor = get_object_or_404(Donor, id=donor_id)
if request.method == 'POST':
form = BloodUnitForm(request.POST)
if form.is_valid():
blood_unit = form.save(commit=False)
blood_unit.collected_by = request.user
blood_unit.save()
# Update donor's last donation date and total donations
if blood_unit.donor:
blood_unit.donor.last_donation_date = blood_unit.collection_date
blood_unit.donor.total_donations += 1
blood_unit.donor.save()
messages.success(request, f'Blood unit {blood_unit.unit_number} created successfully.')
return redirect('blood_bank:blood_unit_detail', unit_id=blood_unit.id)
else:
initial_data = {}
if donor:
initial_data['donor'] = donor
initial_data['blood_group'] = donor.blood_group
form = BloodUnitForm(initial=initial_data)
return render(request, 'blood_bank/blood_unit_form.html', {
'form': form, 'donor': donor, 'title': 'Register Blood Unit'
})
# Blood Request Management Views
@login_required
def blood_request_list(request):
"""List all blood requests"""
requests = BloodRequest.objects.select_related(
'patient', 'requesting_department', 'requesting_physician', 'component_requested'
).order_by('-request_date')
# Filter by status
status_filter = request.GET.get('status')
if status_filter:
requests = requests.filter(status=status_filter)
# Filter by urgency
urgency_filter = request.GET.get('urgency')
if urgency_filter:
requests = requests.filter(urgency=urgency_filter)
paginator = Paginator(requests, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
'status_choices': BloodRequest.STATUS_CHOICES,
'urgency_choices': BloodRequest.URGENCY_CHOICES,
'status_filter': status_filter,
'urgency_filter': urgency_filter,
}
return render(request, 'blood_bank/blood_request_list.html', context)
@login_required
def blood_request_detail(request, request_id):
"""Blood request detail view"""
blood_request = get_object_or_404(BloodRequest, id=request_id)
# Get issued units for this request
issues = BloodIssue.objects.filter(blood_request=blood_request).select_related(
'blood_unit', 'issued_by', 'issued_to'
)
context = {
'blood_request': blood_request,
'issues': issues,
}
return render(request, 'blood_bank/blood_request_detail.html', context)
@login_required
@permission_required('blood_bank.add_bloodrequest')
def blood_request_create(request):
"""Create new blood request"""
if request.method == 'POST':
form = BloodRequestForm(request.POST)
if form.is_valid():
blood_request = form.save(commit=False)
blood_request.requesting_physician = request.user
# Generate request number
blood_request.request_number = f"BR{timezone.now().strftime('%Y%m%d')}{BloodRequest.objects.count() + 1:04d}"
blood_request.save()
messages.success(request, f'Blood request {blood_request.request_number} created successfully.')
return redirect('blood_bank:blood_request_detail', request_id=blood_request.id)
else:
form = BloodRequestForm()
return render(request, 'blood_bank/blood_request_form.html', {
'form': form, 'title': 'Create Blood Request'
})
# Blood Issue and Transfusion Views
@login_required
@permission_required('blood_bank.add_bloodissue')
def blood_issue_create(request, request_id):
"""Issue blood unit for a request"""
blood_request = get_object_or_404(BloodRequest, id=request_id)
if request.method == 'POST':
form = BloodIssueForm(request.POST)
if form.is_valid():
blood_issue = form.save(commit=False)
blood_issue.issued_by = request.user
blood_issue.save()
# Update blood unit status
blood_issue.blood_unit.status = 'issued'
blood_issue.blood_unit.save()
# Update request status
blood_request.status = 'issued'
blood_request.processed_by = request.user
blood_request.processed_at = timezone.now()
blood_request.save()
messages.success(request, f'Blood unit {blood_issue.blood_unit.unit_number} issued successfully.')
return redirect('blood_bank:blood_request_detail', request_id=blood_request.id)
else:
# Filter compatible blood units
compatible_units = BloodUnit.objects.filter(
blood_group=blood_request.patient_blood_group,
component=blood_request.component_requested,
status='available'
)
form = BloodIssueForm(initial={
'blood_request': blood_request,
'expiry_time': timezone.now() + timedelta(hours=4)
})
form.fields['blood_unit'].queryset = compatible_units
context = {
'form': form,
'blood_request': blood_request,
'title': 'Issue Blood Unit'
}
return render(request, 'blood_bank/blood_issue_form.html', context)
@login_required
def transfusion_list(request):
"""List all transfusions"""
transfusions = Transfusion.objects.select_related(
'blood_issue__blood_unit', 'blood_issue__blood_request__patient',
'administered_by'
).order_by('-start_time')
paginator = Paginator(transfusions, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'blood_bank/transfusion_list.html', {'page_obj': page_obj})
@login_required
def transfusion_detail(request, transfusion_id):
"""Transfusion detail view"""
transfusion = get_object_or_404(Transfusion, id=transfusion_id)
# Get adverse reactions
reactions = AdverseReaction.objects.filter(transfusion=transfusion)
context = {
'transfusion': transfusion,
'reactions': reactions,
}
return render(request, 'blood_bank/transfusion_detail.html', context)
@login_required
@permission_required('blood_bank.add_transfusion')
def transfusion_create(request, issue_id):
"""Start transfusion"""
blood_issue = get_object_or_404(BloodIssue, id=issue_id)
if request.method == 'POST':
form = TransfusionForm(request.POST)
if form.is_valid():
transfusion = form.save(commit=False)
transfusion.administered_by = request.user
transfusion.save()
# Update blood unit status
blood_issue.blood_unit.status = 'transfused'
blood_issue.blood_unit.save()
messages.success(request, 'Transfusion started successfully.')
return redirect('blood_bank:transfusion_detail', transfusion_id=transfusion.id)
else:
form = TransfusionForm(initial={
'blood_issue': blood_issue,
'start_time': timezone.now()
})
context = {
'form': form,
'blood_issue': blood_issue,
'title': 'Start Transfusion'
}
return render(request, 'blood_bank/transfusion_form.html', context)
# Testing Views
@login_required
@permission_required('blood_bank.add_bloodtest')
def blood_test_create(request, unit_id):
"""Add test result for blood unit"""
blood_unit = get_object_or_404(BloodUnit, id=unit_id)
if request.method == 'POST':
form = BloodTestForm(request.POST)
if form.is_valid():
test = form.save(commit=False)
test.tested_by = request.user
test.save()
# Update blood unit status based on test results
if test.result == 'positive' and test.test_type in ['hiv', 'hbv', 'hcv', 'syphilis']:
blood_unit.status = 'discarded'
blood_unit.save()
messages.success(request, f'Test result for {test.get_test_type_display()} added successfully.')
return redirect('blood_bank:blood_unit_detail', unit_id=blood_unit.id)
else:
form = BloodTestForm(initial={
'blood_unit': blood_unit,
'test_date': timezone.now()
})
context = {
'form': form,
'blood_unit': blood_unit,
'title': 'Add Test Result'
}
return render(request, 'blood_bank/blood_test_form.html', context)
@login_required
@permission_required('blood_bank.add_crossmatch')
def crossmatch_create(request, unit_id, patient_id):
"""Create crossmatch test"""
blood_unit = get_object_or_404(BloodUnit, id=unit_id)
if request.method == 'POST':
form = CrossMatchForm(request.POST)
if form.is_valid():
crossmatch = form.save(commit=False)
crossmatch.tested_by = request.user
crossmatch.save()
messages.success(request, 'Crossmatch test created successfully.')
return redirect('blood_bank:blood_unit_detail', unit_id=blood_unit.id)
else:
form = CrossMatchForm(initial={
'blood_unit': blood_unit,
'test_date': timezone.now()
})
context = {
'form': form,
'blood_unit': blood_unit,
'title': 'Crossmatch Test'
}
return render(request, 'blood_bank/crossmatch_form.html', context)
# Inventory Management Views
@login_required
def inventory_overview(request):
"""Blood inventory overview"""
# Get inventory by blood group and component
inventory_data = BloodUnit.objects.filter(status='available').values(
'blood_group__abo_type', 'blood_group__rh_factor', 'component__name'
).annotate(count=Count('id')).order_by(
'blood_group__abo_type', 'blood_group__rh_factor', 'component__name'
)
# Get expiring units
expiring_units = BloodUnit.objects.filter(
status='available',
expiry_date__lte=timezone.now() + timedelta(days=7)
).select_related('component', 'blood_group').order_by('expiry_date')
# Get location utilization
locations = InventoryLocation.objects.filter(is_active=True)
context = {
'inventory_data': inventory_data,
'expiring_units': expiring_units,
'locations': locations,
}
return render(request, 'blood_bank/inventory_overview.html', context)
# Quality Control Views
@login_required
def quality_control_list(request):
"""List quality control tests"""
qc_tests = QualityControl.objects.select_related(
'performed_by', 'reviewed_by'
).order_by('-test_date')
paginator = Paginator(qc_tests, 25)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'blood_bank/quality_control_list.html', {'page_obj': page_obj})
@login_required
@permission_required('blood_bank.add_qualitycontrol')
def quality_control_create(request):
"""Create quality control test"""
if request.method == 'POST':
form = QualityControlForm(request.POST)
if form.is_valid():
qc_test = form.save(commit=False)
qc_test.performed_by = request.user
qc_test.save()
messages.success(request, 'Quality control test created successfully.')
return redirect('blood_bank:quality_control_list')
else:
form = QualityControlForm(initial={'test_date': timezone.now()})
return render(request, 'blood_bank/quality_control_form.html', {
'form': form, 'title': 'Create QC Test'
})
# API Views for AJAX requests
@login_required
@require_http_methods(["GET"])
def api_blood_availability(request):
"""API endpoint for checking blood availability"""
blood_group_id = request.GET.get('blood_group')
component_id = request.GET.get('component')
if not blood_group_id or not component_id:
return JsonResponse({'error': 'Missing parameters'}, status=400)
available_units = BloodUnit.objects.filter(
blood_group_id=blood_group_id,
component_id=component_id,
status='available'
).count()
return JsonResponse({'available_units': available_units})
@login_required
@require_http_methods(["GET"])
def api_donor_search(request):
"""API endpoint for donor search"""
query = request.GET.get('q', '')
if len(query) < 2:
return JsonResponse({'donors': []})
donors = Donor.objects.filter(
Q(donor_id__icontains=query) |
Q(first_name__icontains=query) |
Q(last_name__icontains=query)
).filter(status='active')[:10]
donor_data = [{
'id': donor.id,
'donor_id': donor.donor_id,
'name': donor.full_name,
'blood_group': str(donor.blood_group),
'is_eligible': donor.is_eligible_for_donation
} for donor in donors]
return JsonResponse({'donors': donor_data})
# Reports Views
@login_required
def reports_dashboard(request):
"""Blood bank reports dashboard"""
# Get date range from request
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
if not start_date:
start_date = timezone.now() - timedelta(days=30)
else:
start_date = timezone.datetime.strptime(start_date, '%Y-%m-%d').date()
if not end_date:
end_date = timezone.now().date()
else:
end_date = timezone.datetime.strptime(end_date, '%Y-%m-%d').date()
# Collection statistics
collections = BloodUnit.objects.filter(
collection_date__date__range=[start_date, end_date]
)
# Transfusion statistics
transfusions = Transfusion.objects.filter(
start_time__date__range=[start_date, end_date]
)
# Adverse reactions
reactions = AdverseReaction.objects.filter(
onset_time__date__range=[start_date, end_date]
)
context = {
'start_date': start_date,
'end_date': end_date,
'total_collections': collections.count(),
'total_transfusions': transfusions.count(),
'total_reactions': reactions.count(),
'collections_by_type': collections.values('component__name').annotate(count=Count('id')),
'transfusions_by_type': transfusions.values(
'blood_issue__blood_unit__component__name'
).annotate(count=Count('id')),
'reactions_by_type': reactions.values('reaction_type').annotate(count=Count('id')),
}
return render(request, 'blood_bank/reports_dashboard.html', context)
# Additional API Views for JavaScript functions
@login_required
@require_http_methods(["POST"])
def api_move_unit(request, unit_id):
"""API endpoint for moving blood unit to different location"""
try:
unit = BloodUnit.objects.get(id=unit_id)
new_location_id = request.POST.get('location_id')
if not new_location_id:
return JsonResponse({'error': 'Location ID required'}, status=400)
try:
new_location = InventoryLocation.objects.get(id=new_location_id)
except InventoryLocation.DoesNotExist:
return JsonResponse({'error': 'Invalid location'}, status=400)
# Check location capacity
current_units = BloodUnit.objects.filter(
current_location=new_location,
status__in=['available', 'quarantined']
).count()
if current_units >= new_location.capacity:
return JsonResponse({'error': 'Location at capacity'}, status=400)
old_location = unit.current_location
unit.current_location = new_location
unit.save()
return JsonResponse({
'success': True,
'message': f'Unit moved from {old_location} to {new_location}',
'new_location': str(new_location)
})
except BloodUnit.DoesNotExist:
return JsonResponse({'error': 'Blood unit not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
@require_http_methods(["GET"])
def api_expiry_report(request):
"""API endpoint for expiry report"""
days_ahead = int(request.GET.get('days', 7))
expiring_units = BloodUnit.objects.filter(
expiry_date__lte=timezone.now() + timedelta(days=days_ahead),
status='available'
).select_related('blood_group', 'component', 'current_location')
report_data = []
for unit in expiring_units:
days_to_expiry = (unit.expiry_date - timezone.now().date()).days
report_data.append({
'unit_number': unit.unit_number,
'blood_group': str(unit.blood_group),
'component': unit.component.get_name_display(),
'expiry_date': unit.expiry_date.strftime('%Y-%m-%d'),
'days_to_expiry': days_to_expiry,
'location': str(unit.current_location),
'urgency': 'critical' if days_to_expiry <= 2 else 'warning' if days_to_expiry <= 5 else 'normal'
})
return JsonResponse({
'units': report_data,
'total_expiring': len(report_data),
'critical_count': len([u for u in report_data if u['urgency'] == 'critical']),
'warning_count': len([u for u in report_data if u['urgency'] == 'warning'])
})
@login_required
@require_http_methods(["POST"])
def api_cancel_request(request, request_id):
"""API endpoint for cancelling blood request"""
try:
blood_request = BloodRequest.objects.get(id=request_id)
if blood_request.status in ['completed', 'cancelled']:
return JsonResponse({'error': 'Request cannot be cancelled'}, status=400)
cancellation_reason = request.POST.get('reason', '')
if not cancellation_reason:
return JsonResponse({'error': 'Cancellation reason required'}, status=400)
# Release any reserved units
reserved_units = BloodUnit.objects.filter(
reserved_for_request=blood_request,
status='reserved'
)
reserved_units.update(status='available', reserved_for_request=None)
blood_request.status = 'cancelled'
blood_request.cancellation_reason = cancellation_reason
blood_request.cancelled_by = request.user
blood_request.cancellation_date = timezone.now()
blood_request.save()
return JsonResponse({
'success': True,
'message': 'Blood request cancelled successfully',
'released_units': reserved_units.count()
})
except BloodRequest.DoesNotExist:
return JsonResponse({'error': 'Blood request not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
@require_http_methods(["GET"])
def api_check_availability(request):
"""API endpoint for real-time blood availability checking"""
blood_group_id = request.GET.get('blood_group')
component_id = request.GET.get('component')
quantity = int(request.GET.get('quantity', 1))
if not blood_group_id or not component_id:
return JsonResponse({'error': 'Missing parameters'}, status=400)
available_units = BloodUnit.objects.filter(
blood_group_id=blood_group_id,
component_id=component_id,
status='available'
).order_by('expiry_date')
total_available = available_units.count()
expiring_soon = available_units.filter(
expiry_date__lte=timezone.now() + timedelta(days=7)
).count()
# Get units by location
location_breakdown = {}
for unit in available_units:
location = str(unit.current_location)
if location not in location_breakdown:
location_breakdown[location] = 0
location_breakdown[location] += 1
return JsonResponse({
'total_available': total_available,
'requested_quantity': quantity,
'can_fulfill': total_available >= quantity,
'expiring_soon': expiring_soon,
'location_breakdown': location_breakdown,
'oldest_unit_expiry': available_units.first().expiry_date.strftime('%Y-%m-%d') if available_units.exists() else None
})
@login_required
@require_http_methods(["GET"])
def api_urgency_report(request):
"""API endpoint for urgency statistics report"""
# Get requests by urgency level
urgency_stats = BloodRequest.objects.values('urgency').annotate(
count=Count('id'),
pending=Count('id', filter=Q(status='pending')),
completed=Count('id', filter=Q(status='completed'))
)
# Get emergency requests from last 24 hours
emergency_requests = BloodRequest.objects.filter(
urgency='emergency',
created_at__gte=timezone.now() - timedelta(hours=24)
).count()
# Average response time for urgent requests
urgent_completed = BloodRequest.objects.filter(
urgency__in=['urgent', 'emergency'],
status='completed',
completed_at__isnull=False
)
avg_response_times = {}
for request in urgent_completed:
urgency = request.urgency
response_time = (request.completed_at - request.created_at).total_seconds() / 60 # minutes
if urgency not in avg_response_times:
avg_response_times[urgency] = []
avg_response_times[urgency].append(response_time)
# Calculate averages
for urgency in avg_response_times:
times = avg_response_times[urgency]
avg_response_times[urgency] = sum(times) / len(times) if times else 0
return JsonResponse({
'urgency_breakdown': list(urgency_stats),
'emergency_last_24h': emergency_requests,
'avg_response_times': avg_response_times,
'total_pending': BloodRequest.objects.filter(status='pending').count()
})
@login_required
@require_http_methods(["POST"])
def api_initiate_capa(request):
"""API endpoint for initiating CAPA (Corrective and Preventive Action)"""
qc_record_id = request.POST.get('qc_record_id')
priority = request.POST.get('priority', 'medium')
assessment = request.POST.get('assessment', '')
if not qc_record_id:
return JsonResponse({'error': 'QC record ID required'}, status=400)
try:
qc_record = QualityControl.objects.get(id=qc_record_id)
if qc_record.capa_initiated:
return JsonResponse({'error': 'CAPA already initiated'}, status=400)
# Generate CAPA number
capa_number = f"CAPA-{qc_record.id}-{timezone.now().strftime('%Y%m%d')}"
qc_record.capa_initiated = True
qc_record.capa_number = capa_number
qc_record.capa_priority = priority
qc_record.capa_initiated_by = request.user
qc_record.capa_date = timezone.now()
qc_record.capa_assessment = assessment
qc_record.capa_status = 'open'
qc_record.save()
return JsonResponse({
'success': True,
'capa_number': capa_number,
'message': f'CAPA {capa_number} initiated successfully'
})
except QualityControl.DoesNotExist:
return JsonResponse({'error': 'QC record not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
@require_http_methods(["POST"])
def api_review_results(request):
"""API endpoint for reviewing QC results"""
qc_record_id = request.POST.get('qc_record_id')
review_notes = request.POST.get('review_notes', '')
if not qc_record_id:
return JsonResponse({'error': 'QC record ID required'}, status=400)
try:
qc_record = QualityControl.objects.get(id=qc_record_id)
if qc_record.reviewed_by:
return JsonResponse({'error': 'Results already reviewed'}, status=400)
qc_record.reviewed_by = request.user
qc_record.review_date = timezone.now()
qc_record.review_notes = review_notes
qc_record.save()
return JsonResponse({
'success': True,
'reviewed_by': request.user.get_full_name(),
'review_date': qc_record.review_date.strftime('%Y-%m-%d %H:%M'),
'message': 'QC results reviewed successfully'
})
except QualityControl.DoesNotExist:
return JsonResponse({'error': 'QC record not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
@require_http_methods(["POST"])
def api_record_vital_signs(request):
"""API endpoint for recording vital signs during transfusion"""
transfusion_id = request.POST.get('transfusion_id')
if not transfusion_id:
return JsonResponse({'error': 'Transfusion ID required'}, status=400)
try:
transfusion = Transfusion.objects.get(id=transfusion_id)
vital_signs = {
'blood_pressure': request.POST.get('blood_pressure'),
'heart_rate': request.POST.get('heart_rate'),
'temperature': request.POST.get('temperature'),
'respiratory_rate': request.POST.get('respiratory_rate'),
'oxygen_saturation': request.POST.get('oxygen_saturation'),
'recorded_at': timezone.now().isoformat(),
'recorded_by': request.user.get_full_name()
}
# Add to existing vital signs history
if transfusion.vital_signs_history:
vital_signs_list = json.loads(transfusion.vital_signs_history)
else:
vital_signs_list = []
vital_signs_list.append(vital_signs)
transfusion.vital_signs_history = json.dumps(vital_signs_list)
# Update current vital signs
transfusion.current_blood_pressure = vital_signs['blood_pressure']
transfusion.current_heart_rate = vital_signs['heart_rate']
transfusion.current_temperature = vital_signs['temperature']
transfusion.current_respiratory_rate = vital_signs['respiratory_rate']
transfusion.current_oxygen_saturation = vital_signs['oxygen_saturation']
transfusion.last_vitals_check = timezone.now()
transfusion.save()
return JsonResponse({
'success': True,
'message': 'Vital signs recorded successfully',
'vital_signs': vital_signs
})
except Transfusion.DoesNotExist:
return JsonResponse({'error': 'Transfusion not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
@require_http_methods(["POST"])
def api_stop_transfusion(request):
"""API endpoint for emergency stop of transfusion"""
transfusion_id = request.POST.get('transfusion_id')
stop_reason = request.POST.get('stop_reason', '')
if not transfusion_id:
return JsonResponse({'error': 'Transfusion ID required'}, status=400)
try:
transfusion = Transfusion.objects.get(id=transfusion_id)
if transfusion.status not in ['started', 'in_progress']:
return JsonResponse({'error': 'Transfusion cannot be stopped'}, status=400)
transfusion.status = 'stopped'
transfusion.end_time = timezone.now()
transfusion.stop_reason = stop_reason
transfusion.stopped_by = request.user
# Calculate volume transfused based on time elapsed
if transfusion.start_time:
elapsed_minutes = (timezone.now() - transfusion.start_time).total_seconds() / 60
rate_ml_per_min = transfusion.transfusion_rate / 60 if transfusion.transfusion_rate else 0
volume_transfused = min(elapsed_minutes * rate_ml_per_min, transfusion.blood_unit.volume_ml)
transfusion.volume_transfused = volume_transfused
transfusion.save()
return JsonResponse({
'success': True,
'message': 'Transfusion stopped successfully',
'volume_transfused': transfusion.volume_transfused,
'stop_time': transfusion.end_time.strftime('%Y-%m-%d %H:%M:%S')
})
except Transfusion.DoesNotExist:
return JsonResponse({'error': 'Transfusion not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
@require_http_methods(["POST"])
def api_complete_transfusion(request):
"""API endpoint for completing transfusion"""
transfusion_id = request.POST.get('transfusion_id')
completion_notes = request.POST.get('completion_notes', '')
if not transfusion_id:
return JsonResponse({'error': 'Transfusion ID required'}, status=400)
try:
transfusion = Transfusion.objects.get(id=transfusion_id)
if transfusion.status not in ['started', 'in_progress']:
return JsonResponse({'error': 'Transfusion cannot be completed'}, status=400)
transfusion.status = 'completed'
transfusion.end_time = timezone.now()
transfusion.completion_notes = completion_notes
transfusion.completed_by = request.user
# Set volume transfused to full unit if not already set
if not transfusion.volume_transfused:
transfusion.volume_transfused = transfusion.blood_unit.volume_ml
transfusion.save()
# Update blood unit status
blood_unit = transfusion.blood_unit
blood_unit.status = 'transfused'
blood_unit.save()
return JsonResponse({
'success': True,
'message': 'Transfusion completed successfully',
'completion_time': transfusion.end_time.strftime('%Y-%m-%d %H:%M:%S'),
'total_volume': transfusion.volume_transfused
})
except Transfusion.DoesNotExist:
return JsonResponse({'error': 'Transfusion not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
@require_http_methods(["GET"])
def api_export_csv(request):
"""API endpoint for exporting data to CSV"""
import csv
from django.http import HttpResponse
export_type = request.GET.get('type', 'donors')
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="{export_type}_{timezone.now().strftime("%Y%m%d")}.csv"'
writer = csv.writer(response)
if export_type == 'donors':
writer.writerow(['Donor ID', 'Name', 'Blood Group', 'Status', 'Last Donation', 'Total Donations'])
donors = Donor.objects.select_related('blood_group').all()
for donor in donors:
last_donation = donor.blood_units.order_by('-collection_date').first()
writer.writerow([
donor.donor_id,
donor.full_name,
str(donor.blood_group),
donor.get_status_display(),
last_donation.collection_date.strftime('%Y-%m-%d') if last_donation else 'Never',
donor.blood_units.count()
])
elif export_type == 'units':
writer.writerow(['Unit Number', 'Blood Group', 'Component', 'Status', 'Collection Date', 'Expiry Date', 'Location'])
units = BloodUnit.objects.select_related('blood_group', 'component', 'current_location').all()
for unit in units:
writer.writerow([
unit.unit_number,
str(unit.blood_group),
unit.component.get_name_display(),
unit.get_status_display(),
unit.collection_date.strftime('%Y-%m-%d'),
unit.expiry_date.strftime('%Y-%m-%d'),
str(unit.current_location)
])
elif export_type == 'requests':
writer.writerow(['Request Number', 'Patient', 'Blood Group', 'Component', 'Quantity', 'Urgency', 'Status', 'Created Date'])
requests = BloodRequest.objects.select_related('patient', 'component').all()
for req in requests:
writer.writerow([
req.request_number,
req.patient.full_name,
str(req.patient.blood_group),
req.component.get_name_display(),
req.quantity_requested,
req.get_urgency_display(),
req.get_status_display(),
req.created_at.strftime('%Y-%m-%d %H:%M')
])
return response
@login_required
@require_http_methods(["GET"])
def api_inventory_locations(request):
"""API endpoint for inventory location management"""
locations = InventoryLocation.objects.all()
location_data = []
for location in locations:
current_units = BloodUnit.objects.filter(
current_location=location,
status__in=['available', 'quarantined']
).count()
location_data.append({
'id': location.id,
'name': location.name,
'location_type': location.get_location_type_display(),
'capacity': location.capacity,
'current_units': current_units,
'available_space': location.capacity - current_units,
'temperature': location.temperature,
'is_active': location.is_active,
'utilization_percent': round((current_units / location.capacity) * 100, 1) if location.capacity > 0 else 0
})
return JsonResponse({'locations': location_data})
@login_required
@require_http_methods(["POST"])
def api_update_location(request, location_id):
"""API endpoint for updating inventory location"""
try:
location = InventoryLocation.objects.get(id=location_id)
# Update fields if provided
if 'temperature' in request.POST:
location.temperature = float(request.POST['temperature'])
if 'is_active' in request.POST:
location.is_active = request.POST['is_active'].lower() == 'true'
if 'notes' in request.POST:
location.notes = request.POST['notes']
location.save()
return JsonResponse({
'success': True,
'message': 'Location updated successfully',
'location': {
'id': location.id,
'name': location.name,
'temperature': location.temperature,
'is_active': location.is_active
}
})
except InventoryLocation.DoesNotExist:
return JsonResponse({'error': 'Location not found'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)