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)