450 lines
16 KiB
Python
450 lines
16 KiB
Python
"""
|
|
Configuration Console UI views - System configuration management
|
|
"""
|
|
|
|
import json
|
|
|
|
from django.contrib import messages
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.core.paginator import Paginator
|
|
from django.db.models import Q
|
|
from django.http import JsonResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.template.loader import render_to_string
|
|
from django.conf import settings
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from apps.organizations.models import Department, Hospital
|
|
from apps.px_action_center.models import PXActionSLAConfig, RoutingRule
|
|
from apps.complaints.models import OnCallAdminSchedule
|
|
from apps.callcenter.models import CallRecord
|
|
from apps.notifications.services import NotificationService
|
|
from apps.accounts.services import PasswordResetTokenService
|
|
from apps.core.decorators import px_admin_required, admin_required
|
|
from apps.accounts.models import User
|
|
|
|
|
|
@px_admin_required
|
|
def config_dashboard(request):
|
|
"""Configuration dashboard - overview of system settings - PX Admin only"""
|
|
|
|
# Get counts
|
|
sla_configs_count = PXActionSLAConfig.objects.filter(is_active=True).count()
|
|
routing_rules_count = RoutingRule.objects.filter(is_active=True).count()
|
|
hospitals_count = Hospital.objects.filter(status="active").count()
|
|
oncall_schedules_count = OnCallAdminSchedule.objects.filter(is_active=True).count()
|
|
call_records_count = CallRecord.objects.count()
|
|
provisional_users_count = User.objects.filter(is_provisional=True).count()
|
|
active_users_count = User.objects.filter(is_active=True, is_superuser=False, is_provisional=False).count()
|
|
|
|
context = {
|
|
"sla_configs_count": sla_configs_count,
|
|
"routing_rules_count": routing_rules_count,
|
|
"hospitals_count": hospitals_count,
|
|
"oncall_schedules_count": oncall_schedules_count,
|
|
"call_records_count": call_records_count,
|
|
"provisional_users_count": provisional_users_count,
|
|
"active_users_count": active_users_count,
|
|
}
|
|
|
|
return render(request, "config/dashboard.html", context)
|
|
|
|
|
|
@px_admin_required
|
|
def sla_config_list(request):
|
|
"""SLA configurations list view - PX Admin only"""
|
|
|
|
queryset = PXActionSLAConfig.objects.select_related("hospital", "department")
|
|
|
|
# Apply filters
|
|
hospital_filter = request.GET.get("hospital")
|
|
if hospital_filter:
|
|
queryset = queryset.filter(hospital_id=hospital_filter)
|
|
|
|
is_active = request.GET.get("is_active")
|
|
if is_active == "true":
|
|
queryset = queryset.filter(is_active=True)
|
|
elif is_active == "false":
|
|
queryset = queryset.filter(is_active=False)
|
|
|
|
# Ordering
|
|
queryset = queryset.order_by("hospital", "name")
|
|
|
|
# Pagination
|
|
page_size = int(request.GET.get("page_size", 25))
|
|
paginator = Paginator(queryset, page_size)
|
|
page_number = request.GET.get("page", 1)
|
|
page_obj = paginator.get_page(page_number)
|
|
|
|
# Get hospitals for filter
|
|
hospitals = Hospital.objects.filter(status="active")
|
|
|
|
context = {
|
|
"page_obj": page_obj,
|
|
"sla_configs": page_obj.object_list,
|
|
"hospitals": hospitals,
|
|
"filters": request.GET,
|
|
}
|
|
|
|
return render(request, "config/sla_config.html", context)
|
|
|
|
|
|
@px_admin_required
|
|
def routing_rules_list(request):
|
|
"""Routing rules list view - PX Admin only"""
|
|
|
|
queryset = RoutingRule.objects.select_related("hospital", "department", "assign_to_user", "assign_to_department")
|
|
|
|
# Apply filters
|
|
hospital_filter = request.GET.get("hospital")
|
|
if hospital_filter:
|
|
queryset = queryset.filter(hospital_id=hospital_filter)
|
|
|
|
is_active = request.GET.get("is_active")
|
|
if is_active == "true":
|
|
queryset = queryset.filter(is_active=True)
|
|
elif is_active == "false":
|
|
queryset = queryset.filter(is_active=False)
|
|
|
|
# Ordering
|
|
queryset = queryset.order_by("-priority", "name")
|
|
|
|
# Pagination
|
|
page_size = int(request.GET.get("page_size", 25))
|
|
paginator = Paginator(queryset, page_size)
|
|
page_number = request.GET.get("page", 1)
|
|
page_obj = paginator.get_page(page_number)
|
|
|
|
# Get hospitals for filter
|
|
hospitals = Hospital.objects.filter(status="active")
|
|
|
|
context = {
|
|
"page_obj": page_obj,
|
|
"routing_rules": page_obj.object_list,
|
|
"hospitals": hospitals,
|
|
"filters": request.GET,
|
|
}
|
|
|
|
return render(request, "config/routing_rules.html", context)
|
|
|
|
|
|
@admin_required
|
|
def hospital_users_list(request):
|
|
"""Hospital users list view - PX Admin and Hospital Admin"""
|
|
|
|
queryset = User.objects.select_related("hospital", "department").filter(is_superuser=False, is_provisional=False)
|
|
|
|
if not request.user.is_px_admin():
|
|
if request.tenant_hospital:
|
|
queryset = queryset.filter(hospital=request.tenant_hospital)
|
|
else:
|
|
queryset = queryset.none()
|
|
|
|
hospital_filter = request.GET.get("hospital")
|
|
if hospital_filter:
|
|
queryset = queryset.filter(hospital_id=hospital_filter)
|
|
|
|
role_filter = request.GET.get("role")
|
|
if role_filter:
|
|
queryset = queryset.filter(groups__name=role_filter)
|
|
|
|
is_active = request.GET.get("is_active")
|
|
if is_active == "true":
|
|
queryset = queryset.filter(is_active=True)
|
|
elif is_active == "false":
|
|
queryset = queryset.filter(is_active=False)
|
|
|
|
search_query = request.GET.get("search")
|
|
if search_query:
|
|
queryset = queryset.filter(
|
|
Q(first_name__icontains=search_query)
|
|
| Q(last_name__icontains=search_query)
|
|
| Q(email__icontains=search_query)
|
|
| Q(employee_id__icontains=search_query)
|
|
)
|
|
|
|
queryset = queryset.order_by("-date_joined")
|
|
|
|
page_size = int(request.GET.get("page_size", 25))
|
|
paginator = Paginator(queryset, page_size)
|
|
page_number = request.GET.get("page", 1)
|
|
page_obj = paginator.get_page(page_number)
|
|
|
|
if request.user.is_px_admin():
|
|
hospitals = Hospital.objects.filter(status="active").order_by("name")
|
|
elif request.tenant_hospital:
|
|
hospitals = Hospital.objects.filter(id=request.tenant_hospital.id)
|
|
else:
|
|
hospitals = Hospital.objects.none()
|
|
|
|
from django.contrib.auth.models import Group
|
|
|
|
roles = Group.objects.all().order_by("name")
|
|
|
|
total_users = paginator.count
|
|
active_count = queryset.filter(is_active=True).count()
|
|
inactive_count = queryset.filter(is_active=False).count()
|
|
|
|
context = {
|
|
"page_obj": page_obj,
|
|
"users": page_obj.object_list,
|
|
"hospitals": hospitals,
|
|
"roles": roles,
|
|
"filters": request.GET,
|
|
"total_users": total_users,
|
|
"active_count": active_count,
|
|
"inactive_count": inactive_count,
|
|
}
|
|
|
|
return render(request, "config/hospital_users.html", context)
|
|
|
|
|
|
@admin_required
|
|
def user_create(request):
|
|
"""Create a new user from the config console"""
|
|
from apps.accounts.forms import UserCreateForm
|
|
|
|
if not request.user.is_px_admin() and not request.user.is_hospital_admin():
|
|
from django.http import HttpResponseForbidden
|
|
return HttpResponseForbidden(_("You don't have permission to create users."))
|
|
|
|
if request.method == "POST":
|
|
form = UserCreateForm(request.POST, request=request)
|
|
if form.is_valid():
|
|
user = form.save()
|
|
generated_password = getattr(form, '_generated_password', None)
|
|
|
|
staff_id = request.POST.get("staff_id")
|
|
if staff_id:
|
|
from apps.organizations.models import Staff
|
|
try:
|
|
staff = Staff.objects.get(pk=staff_id, user__isnull=True)
|
|
staff.user = user
|
|
staff.save(update_fields=["user"])
|
|
messages.success(request, _("User '{}' created and linked to staff '{}'.").format(
|
|
user.get_full_name(), staff.first_name + " " + staff.last_name
|
|
))
|
|
except Staff.DoesNotExist:
|
|
messages.success(request, _("User '{}' created successfully.").format(user.get_full_name()))
|
|
else:
|
|
messages.success(request, _("User '{}' created successfully.").format(user.get_full_name()))
|
|
|
|
try:
|
|
base_url = f"{request.scheme}://{request.get_host()}"
|
|
reset_token = PasswordResetTokenService.create_reset_token(user)
|
|
reset_url = PasswordResetTokenService.build_reset_url(base_url, reset_token)
|
|
|
|
if generated_password:
|
|
html_message = render_to_string(
|
|
"config/emails/user_created_email.html",
|
|
{"user": user, "reset_url": reset_url, "temp_password": generated_password},
|
|
request=request,
|
|
)
|
|
plain_message = (
|
|
f"Dear {user.get_full_name()},\n\n"
|
|
f"An account has been created for you on PX360.\n\n"
|
|
f"Your temporary password: {generated_password}\n\n"
|
|
f"For security, please set your own password using the link below:\n{reset_url}\n\n"
|
|
f"This link expires in 24 hours."
|
|
)
|
|
subject = _("Your PX360 Account Has Been Created")
|
|
else:
|
|
html_message = render_to_string(
|
|
"config/emails/reset_password_email.html",
|
|
{"user": user, "reset_url": reset_url},
|
|
request=request,
|
|
)
|
|
plain_message = (
|
|
f"Dear {user.get_full_name()},\n\n"
|
|
f"An account has been created for you on PX360.\n\n"
|
|
f"Use the link below to set your password:\n{reset_url}\n\n"
|
|
f"This link expires in 24 hours."
|
|
)
|
|
subject = _("Your PX360 Account Has Been Created")
|
|
|
|
NotificationService.send_email(
|
|
email=user.email,
|
|
subject=subject,
|
|
message=plain_message,
|
|
html_message=html_message,
|
|
user=user,
|
|
notification_type="system",
|
|
)
|
|
if generated_password:
|
|
messages.info(request, _("Credentials sent to {}.").format(user.email))
|
|
else:
|
|
messages.info(request, _("Password setup link sent to {}.").format(user.email))
|
|
except Exception as e:
|
|
messages.warning(request, _("User created but email sending failed: {}").format(str(e)))
|
|
|
|
return redirect("config:hospital_users_list")
|
|
else:
|
|
form = UserCreateForm(request=request)
|
|
|
|
context = {"form": form}
|
|
return render(request, "config/user_form.html", context)
|
|
|
|
|
|
@admin_required
|
|
def user_edit(request, user_id):
|
|
"""Edit an existing user from the config console"""
|
|
from apps.accounts.forms import UserEditForm
|
|
|
|
target_user = get_object_or_404(User, pk=user_id, is_superuser=False, is_provisional=False)
|
|
|
|
if not request.user.is_px_admin():
|
|
if request.tenant_hospital and target_user.hospital != request.tenant_hospital:
|
|
from django.http import HttpResponseForbidden
|
|
return HttpResponseForbidden(_("You can only edit users in your hospital."))
|
|
|
|
if request.method == "POST":
|
|
form = UserEditForm(request.POST, instance=target_user, request=request)
|
|
if form.is_valid():
|
|
form.save()
|
|
messages.success(request, _("User '{}' updated successfully.").format(target_user.get_full_name()))
|
|
return redirect("config:hospital_users_list")
|
|
else:
|
|
form = UserEditForm(instance=target_user, request=request)
|
|
|
|
context = {"form": form, "edit_user": target_user}
|
|
return render(request, "config/user_form.html", context)
|
|
|
|
|
|
@admin_required
|
|
def reset_user_password(request, user_id):
|
|
"""Reset a user's password and send them the new credentials via email."""
|
|
|
|
if request.method != "POST":
|
|
return JsonResponse({"error": "Method not allowed"}, status=405)
|
|
|
|
target_user = get_object_or_404(User, pk=user_id, is_superuser=False)
|
|
|
|
if not request.user.is_px_admin():
|
|
if request.tenant_hospital and target_user.hospital != request.tenant_hospital:
|
|
return JsonResponse({"error": "You can only reset passwords for users in your hospital."}, status=403)
|
|
|
|
if target_user.is_provisional:
|
|
return JsonResponse({"error": "Use the onboarding invitation flow for provisional users."}, status=400)
|
|
|
|
base_url = f"{request.scheme}://{request.get_host()}"
|
|
reset_token = PasswordResetTokenService.create_reset_token(target_user)
|
|
reset_url = PasswordResetTokenService.build_reset_url(base_url, reset_token)
|
|
|
|
html_message = render_to_string(
|
|
"config/emails/reset_password_email.html",
|
|
{
|
|
"user": target_user,
|
|
"reset_url": reset_url,
|
|
},
|
|
request=request,
|
|
)
|
|
|
|
plain_message = (
|
|
f"Dear {target_user.get_full_name()},\n\n"
|
|
f"Your PX360 password has been reset by an administrator.\n\n"
|
|
f"For your security, no password is sent by email. Use the link below to set a new password:\n"
|
|
f"{reset_url}\n\n"
|
|
f"This link expires in 24 hours. If you did not expect this reset, contact your system administrator."
|
|
)
|
|
|
|
NotificationService.send_email(
|
|
email=target_user.email,
|
|
subject=_("Your PX360 Password Has Been Reset"),
|
|
message=plain_message,
|
|
html_message=html_message,
|
|
user=target_user,
|
|
notification_type="system",
|
|
)
|
|
|
|
return JsonResponse(
|
|
{
|
|
"success": True,
|
|
"message": f"Password reset link has been sent to {target_user.email}.",
|
|
"user_name": target_user.get_full_name(),
|
|
"user_email": target_user.email,
|
|
}
|
|
)
|
|
|
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django.views.decorators.http import require_http_methods
|
|
from rich import print
|
|
from django.contrib.auth.hashers import check_password as verify_password
|
|
|
|
|
|
@admin_required
|
|
def toggle_user_active(request, user_id):
|
|
if request.method != "POST":
|
|
return JsonResponse({"error": "Method not allowed"}, status=405)
|
|
|
|
target_user = get_object_or_404(User, pk=user_id, is_superuser=False, is_provisional=False)
|
|
|
|
if not request.user.is_px_admin():
|
|
return JsonResponse({"error": "Only PX Admins can activate/deactivate users."}, status=403)
|
|
|
|
data = json.loads(request.body) if request.body else {}
|
|
password = data.get("password", "")
|
|
|
|
if not verify_password(password, request.user.password):
|
|
return JsonResponse({"error": "Incorrect password. Please try again."}, status=400)
|
|
|
|
target_user.is_active = not target_user.is_active
|
|
target_user.save(update_fields=["is_active"])
|
|
|
|
status_label = "activated" if target_user.is_active else "deactivated"
|
|
|
|
return JsonResponse(
|
|
{
|
|
"success": True,
|
|
"is_active": target_user.is_active,
|
|
"message": f"User {target_user.get_full_name()} has been {status_label}.",
|
|
"user_name": target_user.get_full_name(),
|
|
}
|
|
)
|
|
|
|
|
|
@csrf_exempt
|
|
@require_http_methods(["POST"])
|
|
def test(request):
|
|
import json
|
|
from django.http import JsonResponse
|
|
|
|
print(json.loads(request.body))
|
|
|
|
return JsonResponse({"status": "ok"})
|
|
|
|
|
|
@login_required
|
|
def deleted_items(request):
|
|
"""View deleted items across all models - PX Admin only"""
|
|
if not request.user.is_px_admin():
|
|
from django.http import HttpResponseForbidden
|
|
return HttpResponseForbidden(_("You don't have permission to view deleted items."))
|
|
|
|
from apps.complaints.models import Complaint, Inquiry
|
|
from apps.observations.models import Observation
|
|
from apps.appreciation.models import Appreciation
|
|
|
|
deleted_complaints = Complaint.all_objects.filter(is_deleted=True).select_related(
|
|
"hospital", "department", "created_by"
|
|
).order_by("-deleted_at")
|
|
|
|
deleted_observations = Observation.all_objects.filter(is_deleted=True).select_related(
|
|
"hospital", "assigned_department"
|
|
).order_by("-deleted_at")
|
|
|
|
deleted_inquiries = Inquiry.all_objects.filter(is_deleted=True).select_related(
|
|
"hospital", "department"
|
|
).order_by("-deleted_at")
|
|
|
|
deleted_appreciations = Appreciation.all_objects.filter(is_deleted=True).select_related(
|
|
"hospital", "sender"
|
|
).order_by("-deleted_at")
|
|
|
|
return render(request, "config/deleted_items.html", {
|
|
"deleted_complaints": deleted_complaints,
|
|
"deleted_observations": deleted_observations,
|
|
"deleted_inquiries": deleted_inquiries,
|
|
"deleted_appreciations": deleted_appreciations,
|
|
})
|