HH/apps/standards/views.py
2026-01-15 15:02:42 +03:00

592 lines
19 KiB
Python

from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
from django.http import JsonResponse
from django.db.models import Count, Q
from django.utils import timezone
from datetime import datetime
import json
from apps.standards.models import (
StandardSource,
StandardCategory,
Standard,
StandardCompliance,
StandardAttachment
)
from apps.organizations.models import Department
from apps.standards.forms import (
StandardSourceForm,
StandardCategoryForm,
StandardForm,
StandardComplianceForm,
StandardAttachmentForm
)
# ==================== API ViewSets ====================
class StandardSourceViewSet(viewsets.ModelViewSet):
queryset = StandardSource.objects.all()
filterset_fields = ['is_active']
search_fields = ['name', 'name_ar', 'code']
ordering = ['name']
def get_serializer_class(self):
from apps.standards.serializers import StandardSourceSerializer
return StandardSourceSerializer
class StandardCategoryViewSet(viewsets.ModelViewSet):
queryset = StandardCategory.objects.all()
filterset_fields = ['is_active']
search_fields = ['name', 'name_ar']
ordering = ['order', 'name']
def get_serializer_class(self):
from apps.standards.serializers import StandardCategorySerializer
return StandardCategorySerializer
class StandardViewSet(viewsets.ModelViewSet):
queryset = Standard.objects.all()
filterset_fields = ['source', 'category', 'department', 'is_active']
search_fields = ['code', 'title', 'title_ar', 'description']
ordering = ['source', 'category', 'code']
def get_serializer_class(self):
from apps.standards.serializers import StandardSerializer
return StandardSerializer
class StandardComplianceViewSet(viewsets.ModelViewSet):
queryset = StandardCompliance.objects.all()
filterset_fields = ['department', 'standard', 'status']
search_fields = ['department__name', 'standard__code', 'notes']
ordering = ['-created_at']
def get_serializer_class(self):
from apps.standards.serializers import StandardComplianceSerializer
return StandardComplianceSerializer
class StandardAttachmentViewSet(viewsets.ModelViewSet):
queryset = StandardAttachment.objects.all()
filterset_fields = ['compliance']
search_fields = ['filename', 'description']
ordering = ['-uploaded_at']
def get_serializer_class(self):
from apps.standards.serializers import StandardAttachmentSerializer
return StandardAttachmentSerializer
# ==================== UI Views ====================
@login_required
def standards_dashboard(request):
"""Standards dashboard with statistics"""
# Get current hospital from tenant_hospital (set by middleware)
hospital = getattr(request, 'tenant_hospital', None)
if not hospital:
return render(request, 'core/no_hospital_assigned.html')
departments = hospital.departments.filter(status='active')
# Get compliance statistics
compliance_records = StandardCompliance.objects.filter(
department__hospital=hospital
)
stats = {
'total_standards': Standard.objects.filter(is_active=True).count(),
'total_departments': departments.count(),
'met': compliance_records.filter(status='met').count(),
'partially_met': compliance_records.filter(status='partially_met').count(),
'not_met': compliance_records.filter(status='not_met').count(),
'not_assessed': compliance_records.filter(status='not_assessed').count(),
}
# Recent compliance updates
recent_updates = compliance_records.order_by('-updated_at')[:10]
context = {
'hospital': hospital,
'departments': departments,
'stats': stats,
'recent_updates': recent_updates,
}
return render(request, 'standards/dashboard.html', context)
@ensure_csrf_cookie
@login_required
def department_standards_view(request, pk):
"""View all standards for a department"""
department = get_object_or_404(Department, pk=pk)
# Check if user is PX admin
hospital = getattr(request, 'tenant_hospital', None)
is_px_admin = request.user.is_superuser or (
hasattr(request.user, 'hospital_user') and
request.user.hospital_user.hospital == hospital and
request.user.hospital_user.role == 'px_admin'
) if hospital else False
# Get all active standards (both department-specific and general)
department_standards = Standard.objects.filter(is_active=True).filter(
Q(department=department) | Q(department__isnull=True)
).order_by('source', 'category', 'code')
# Get compliance status for each standard
standards_data = []
for standard in department_standards:
compliance = StandardCompliance.objects.filter(
department=department,
standard=standard
).first()
standards_data.append({
'standard': standard,
'compliance': compliance,
'attachment_count': compliance.attachments.count() if compliance else 0,
})
context = {
'department': department,
'standards_data': standards_data,
'is_px_admin': is_px_admin,
}
return render(request, 'standards/department_standards.html', context)
@login_required
def standard_detail(request, pk):
"""View standard details and compliance history"""
standard = get_object_or_404(Standard, pk=pk)
# Get compliance records for all departments
compliance_records = StandardCompliance.objects.filter(
standard=standard
).select_related('department', 'assessor').order_by('-created_at')
context = {
'standard': standard,
'compliance_records': compliance_records,
}
return render(request, 'standards/standard_detail.html', context)
@login_required
def standard_compliance_update(request, compliance_id):
"""Update compliance status"""
compliance = get_object_or_404(StandardCompliance, pk=compliance_id)
if request.method == 'POST':
form = StandardComplianceForm(request.POST, instance=compliance)
if form.is_valid():
form.save()
return redirect('standards:department_standards', pk=compliance.department.pk)
else:
form = StandardComplianceForm(instance=compliance)
context = {
'compliance': compliance,
'form': form,
}
return render(request, 'standards/compliance_form.html', context)
@login_required
def standard_attachment_upload(request, compliance_id):
"""Upload attachment for compliance"""
compliance = get_object_or_404(StandardCompliance, pk=compliance_id)
if request.method == 'POST':
form = StandardAttachmentForm(request.POST, request.FILES)
if form.is_valid():
attachment = form.save(commit=False)
attachment.compliance = compliance
attachment.uploaded_by = request.user
attachment.filename = request.FILES['file'].name
attachment.save()
return redirect('standards:standard_compliance_update', compliance_id=compliance.pk)
else:
form = StandardAttachmentForm()
context = {
'compliance': compliance,
'form': form,
}
return render(request, 'standards/attachment_upload.html', context)
@login_required
def standards_search(request):
"""Search standards"""
query = request.GET.get('q', '')
source_filter = request.GET.get('source', '')
category_filter = request.GET.get('category', '')
status_filter = request.GET.get('status', '')
# Get current hospital from tenant_hospital (set by middleware)
hospital = getattr(request, 'tenant_hospital', None)
if not hospital:
return render(request, 'core/no_hospital_assigned.html')
# Build queryset
standards = Standard.objects.filter(is_active=True)
if query:
standards = standards.filter(
Q(code__icontains=query) |
Q(title__icontains=query) |
Q(title_ar__icontains=query) |
Q(description__icontains=query)
)
if source_filter:
standards = standards.filter(source_id=source_filter)
if category_filter:
standards = standards.filter(category_id=category_filter)
standards = standards.select_related('source', 'category').order_by('source', 'category', 'code')
# Get filters
sources = StandardSource.objects.filter(is_active=True)
categories = StandardCategory.objects.filter(is_active=True)
context = {
'hospital': hospital,
'standards': standards,
'query': query,
'source_filter': source_filter,
'category_filter': category_filter,
'sources': sources,
'categories': categories,
}
return render(request, 'standards/search.html', context)
@login_required
def standard_create(request, department_id=None):
"""Create a new standard (PX Admin only)"""
# Check if user is PX admin
hospital = getattr(request, 'tenant_hospital', None)
if not hospital:
return render(request, 'core/no_hospital_assigned.html')
is_px_admin = request.user.is_superuser or (
hasattr(request.user, 'hospital_user') and
request.user.hospital_user.hospital == hospital and
request.user.hospital_user.role == 'px_admin'
)
if not is_px_admin:
from django.contrib import messages
messages.error(request, 'You do not have permission to create standards.')
if department_id:
return redirect('standards:department_standards', pk=department_id)
return redirect('standards:dashboard')
if request.method == 'POST':
form = StandardForm(request.POST)
if form.is_valid():
standard = form.save(commit=False)
standard.save()
from django.contrib import messages
messages.success(request, 'Standard created successfully.')
if department_id:
return redirect('standards:department_standards', pk=department_id)
return redirect('standards:dashboard')
else:
form = StandardForm()
# If department_id is provided, pre-select that department
if department_id:
from apps.organizations.models import Department
department = Department.objects.filter(pk=department_id).first()
if department:
form.fields['department'].initial = department
# Get all departments for the hospital
departments = hospital.departments.filter(status='active')
context = {
'form': form,
'department_id': department_id,
'departments': departments,
'hospital': hospital,
}
return render(request, 'standards/standard_form.html', context)
@csrf_exempt
@login_required
def create_compliance_ajax(request):
"""Create compliance record via AJAX"""
if not request.user.is_authenticated:
return JsonResponse({'success': False, 'error': 'Authentication required'}, status=401)
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request method'})
# Parse JSON from request body
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({'success': False, 'error': 'Invalid JSON'})
department_id = data.get('department_id')
standard_id = data.get('standard_id')
if not department_id or not standard_id:
return JsonResponse({'success': False, 'error': 'Missing required fields'})
try:
department = Department.objects.get(pk=department_id)
standard = Standard.objects.get(pk=standard_id)
# Check if compliance already exists
compliance, created = StandardCompliance.objects.get_or_create(
department=department,
standard=standard,
defaults={
'assessor': request.user,
'last_assessed_date': timezone.now().date(),
}
)
return JsonResponse({
'success': True,
'compliance_id': compliance.id,
'status': compliance.status,
'created': created,
})
except Exception as e:
import traceback
traceback.print_exc()
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def update_compliance_ajax(request):
"""Update compliance record via AJAX"""
if not request.user.is_authenticated:
return JsonResponse({'success': False, 'error': 'Authentication required'}, status=401)
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request method'})
# Parse JSON from request body
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({'success': False, 'error': 'Invalid JSON'})
compliance_id = data.get('compliance_id')
status = data.get('status')
notes = data.get('notes', '')
evidence_summary = data.get('evidence_summary', '')
last_assessed_date_str = data.get('last_assessed_date')
assessor_id = data.get('assessor_id')
if not compliance_id or not status:
return JsonResponse({'success': False, 'error': 'Missing required fields'})
try:
compliance = StandardCompliance.objects.get(pk=compliance_id)
compliance.status = status
compliance.notes = notes
compliance.evidence_summary = evidence_summary
# Set assessor - use logged-in user or provided ID
if assessor_id:
from apps.accounts.models import User
try:
assessor = User.objects.get(pk=assessor_id)
compliance.assessor = assessor
except User.DoesNotExist:
compliance.assessor = request.user
else:
compliance.assessor = request.user
# Set assessment date
if last_assessed_date_str:
compliance.last_assessed_date = datetime.strptime(last_assessed_date_str, '%Y-%m-%d').date()
else:
compliance.last_assessed_date = timezone.now().date()
compliance.save()
return JsonResponse({
'success': True,
'status': compliance.status,
'status_display': compliance.get_status_display(),
})
except Exception as e:
import traceback
traceback.print_exc()
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def get_compliance_status(request, department_id, standard_id):
"""API endpoint to get compliance status"""
compliance = StandardCompliance.objects.filter(
department_id=department_id,
standard_id=standard_id
).first()
if compliance:
data = {
'status': compliance.status,
'last_assessed_date': compliance.last_assessed_date,
'assessor': compliance.assessor.get_full_name() if compliance.assessor else None,
'notes': compliance.notes,
'attachment_count': compliance.attachments.count(),
}
else:
data = {
'status': 'not_assessed',
'last_assessed_date': None,
'assessor': None,
'notes': '',
'attachment_count': 0,
}
return JsonResponse(data)
# ==================== Source Management Views ====================
@login_required
def source_list(request):
"""List all standard sources"""
sources = StandardSource.objects.all().order_by('name')
context = {'sources': sources}
return render(request, 'standards/source_list.html', context)
@login_required
def source_create(request):
"""Create a new standard source"""
if request.method == 'POST':
form = StandardSourceForm(request.POST)
if form.is_valid():
form.save()
from django.contrib import messages
messages.success(request, 'Source created successfully.')
return redirect('standards:source_list')
else:
form = StandardSourceForm()
context = {'form': form}
return render(request, 'standards/source_form.html', context)
@login_required
def source_update(request, pk):
"""Update a standard source"""
source = get_object_or_404(StandardSource, pk=pk)
if request.method == 'POST':
form = StandardSourceForm(request.POST, instance=source)
if form.is_valid():
form.save()
from django.contrib import messages
messages.success(request, 'Source updated successfully.')
return redirect('standards:source_list')
else:
form = StandardSourceForm(instance=source)
context = {'form': form, 'source': source}
return render(request, 'standards/source_form.html', context)
@login_required
def source_delete(request, pk):
"""Delete a standard source"""
source = get_object_or_404(StandardSource, pk=pk)
if request.method == 'POST':
source.delete()
from django.contrib import messages
messages.success(request, 'Source deleted successfully.')
return redirect('standards:source_list')
context = {'source': source}
return render(request, 'standards/source_confirm_delete.html', context)
# ==================== Category Management Views ====================
@login_required
def category_list(request):
"""List all standard categories"""
categories = StandardCategory.objects.all().order_by('order', 'name')
context = {'categories': categories}
return render(request, 'standards/category_list.html', context)
@login_required
def category_create(request):
"""Create a new standard category"""
if request.method == 'POST':
form = StandardCategoryForm(request.POST)
if form.is_valid():
form.save()
from django.contrib import messages
messages.success(request, 'Category created successfully.')
return redirect('standards:category_list')
else:
form = StandardCategoryForm()
context = {'form': form}
return render(request, 'standards/category_form.html', context)
@login_required
def category_update(request, pk):
"""Update a standard category"""
category = get_object_or_404(StandardCategory, pk=pk)
if request.method == 'POST':
form = StandardCategoryForm(request.POST, instance=category)
if form.is_valid():
form.save()
from django.contrib import messages
messages.success(request, 'Category updated successfully.')
return redirect('standards:category_list')
else:
form = StandardCategoryForm(instance=category)
context = {'form': form, 'category': category}
return render(request, 'standards/category_form.html', context)
@login_required
def category_delete(request, pk):
"""Delete a standard category"""
category = get_object_or_404(StandardCategory, pk=pk)
if request.method == 'POST':
category.delete()
from django.contrib import messages
messages.success(request, 'Category deleted successfully.')
return redirect('standards:category_list')
context = {'category': category}
return render(request, 'standards/category_confirm_delete.html', context)