Skip to content

Commit

Permalink
Merge branch 'develop' into adding-sms-providers
Browse files Browse the repository at this point in the history
  • Loading branch information
DraKen0009 authored Jan 3, 2025
2 parents 98cfe0f + ffbbf3f commit 617b8ae
Show file tree
Hide file tree
Showing 41 changed files with 889 additions and 890 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ load-db:
docker compose exec db sh -c "pg_restore -U postgres --clean --if-exists -d care /tmp/care_db.dump"

reset-db:
docker compose exec backend bash -c "python manage.py reset_db --noinput"
docker compose exec db sh -c "dropdb -U postgres care -f"
docker compose exec db sh -c "createdb -U postgres care"

ruff-all:
ruff check .
Expand Down
156 changes: 155 additions & 1 deletion care/emr/api/viewsets/encounter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import tempfile

from django.core.validators import validate_email as django_validate_email
from django.db import transaction
from django.http import HttpResponse
from django.utils import timezone
from django_filters import rest_framework as filters
from pydantic import UUID4, BaseModel
from drf_spectacular.utils import extend_schema
from pydantic import UUID4, BaseModel, field_validator
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.generics import get_object_or_404
Expand All @@ -17,8 +24,10 @@
Encounter,
EncounterOrganization,
FacilityOrganization,
FileUpload,
Patient,
)
from care.emr.reports import discharge_summary
from care.emr.resources.encounter.constants import COMPLETED_CHOICES
from care.emr.resources.encounter.spec import (
EncounterCreateSpec,
Expand All @@ -27,6 +36,18 @@
EncounterUpdateSpec,
)
from care.emr.resources.facility_organization.spec import FacilityOrganizationReadSpec
from care.emr.resources.file_upload.spec import (
FileCategoryChoices,
FileTypeChoices,
FileUploadRetrieveSpec,
)
from care.emr.tasks.discharge_summary import (
email_discharge_summary_task,
generate_discharge_summary_task,
)
from care.facility.api.serializers.patient_consultation import (
EmailDischargeSummarySerializer,
)
from care.facility.models import Facility
from care.security.authorization import AuthorizationController

Expand Down Expand Up @@ -196,3 +217,136 @@ def organizations_remove(self, request, *args, **kwargs):
encounter=instance, organization=organization
).delete()
return Response({}, status=204)

def _check_discharge_summary_access(self, encounter):
if not AuthorizationController.call(
"can_view_clinical_data", self.request.user, encounter.patient
):
raise PermissionDenied("Permission denied to user")

def _generate_discharge_summary(self, encounter_ext_id: str):
if current_progress := discharge_summary.get_progress(encounter_ext_id):
return Response(
{
"detail": (
"Discharge Summary is already being generated, "
f"current progress {current_progress}%"
)
},
status=status.HTTP_406_NOT_ACCEPTABLE,
)
discharge_summary.set_lock(encounter_ext_id, 1)
generate_discharge_summary_task.delay(encounter_ext_id)
return Response(
{"detail": "Discharge Summary will be generated shortly"},
status=status.HTTP_202_ACCEPTED,
)

@extend_schema(
description="Generate a discharge summary",
responses={
200: "Success",
},
tags=["encounter"],
)
@action(detail=True, methods=["POST"])
def generate_discharge_summary(self, request, *args, **kwargs):
encounter = self.get_object()
self._check_discharge_summary_access(encounter)
return self._generate_discharge_summary(encounter.external_id)

@extend_schema(
description="Get the discharge summary",
responses={200: "Success"},
tags=["encounter"],
)
@action(detail=True, methods=["GET"])
def preview_discharge_summary(self, request, *args, **kwargs):
encounter = self.get_object()
self._check_discharge_summary_access(encounter)
summary_file = (
FileUpload.objects.filter(
file_type=FileTypeChoices.encounter.value,
file_category=FileCategoryChoices.discharge_summary.value,
associating_id=encounter.external_id,
upload_completed=True,
)
.order_by("id")
.last()
)
if summary_file:
return Response(FileUploadRetrieveSpec.serialize(summary_file).to_json())
return self._generate_discharge_summary(encounter.external_id)

class EmailDischargeSummarySpec(BaseModel):
email: str

@field_validator("email")
@classmethod
def validate_email(cls, value):
django_validate_email(value)
return value

@extend_schema(
description="Email the discharge summary to the user",
request=EmailDischargeSummarySerializer,
responses={200: "Success"},
tags=["encounter"],
)
@action(detail=True, methods=["POST"])
def email_discharge_summary(self, request, *args, **kwargs):
encounter = self.get_object()
self._check_discharge_summary_access(encounter)
encounter_ext_id = encounter.external_id
if existing_progress := discharge_summary.get_progress(encounter_ext_id):
return Response(
{
"detail": (
"Discharge Summary is already being generated, "
f"current progress {existing_progress}%"
)
},
status=status.HTTP_406_NOT_ACCEPTABLE,
)

request_data = self.EmailDischargeSummarySpec(**request.data)
email = request_data.email
summary_file = (
FileUpload.objects.filter(
file_type=FileTypeChoices.encounter.value,
file_category=FileCategoryChoices.discharge_summary.value,
associating_id=encounter_ext_id,
upload_completed=True,
)
.order_by("id")
.last()
)
if not summary_file:
(
generate_discharge_summary_task.s(encounter_ext_id)
| email_discharge_summary_task.s(emails=[email])
).delay()
else:
email_discharge_summary_task.delay(summary_file.id, [email])
return Response(
{"detail": "Discharge Summary will be emailed shortly"},
status=status.HTTP_202_ACCEPTED,
)


def dev_preview_discharge_summary(request, encounter_id):
"""
This is a dev only view to preview the discharge summary template
"""
encounter = get_object_or_404(Encounter, external_id=encounter_id)
data = discharge_summary.get_discharge_summary_data(encounter)
data["date"] = timezone.now()

with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
discharge_summary.generate_discharge_summary_pdf(data, tmp_file)
tmp_file.seek(0)

response = HttpResponse(tmp_file, content_type="application/pdf")
response["Content-Disposition"] = 'inline; filename="discharge_summary.pdf"'

return response
2 changes: 1 addition & 1 deletion care/emr/api/viewsets/facility.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def get_queryset(self):
return User.objects.filter(
id__in=SchedulableUserResource.objects.filter(
facility__external_id=self.kwargs["facility_external_id"]
).values("resource_id")
).values("user_id")
)


Expand Down
28 changes: 16 additions & 12 deletions care/emr/api/viewsets/scheduling/availability.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
from django.utils import timezone
from pydantic import UUID4, BaseModel, model_validator
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response

from care.emr.api.viewsets.base import EMRBaseViewSet, EMRRetrieveMixin
Expand All @@ -20,12 +21,13 @@
TokenBookingReadSpec,
TokenSlotBaseSpec,
)
from care.security.authorization import AuthorizationController
from care.users.models import User
from care.utils.lock import Lock


class SlotsForDayRequestSpec(BaseModel):
resource: UUID4
user: UUID4
day: datetime.date


Expand All @@ -37,7 +39,7 @@ class AppointmentBookingSpec(BaseModel):
class AvailabilityStatsRequestSpec(BaseModel):
from_date: datetime.date
to_date: datetime.date
resource: UUID4
user: UUID4

@model_validator(mode="after")
def validate_period(self):
Expand Down Expand Up @@ -104,12 +106,10 @@ def get_slots_for_day(self, request, *args, **kwargs):
@classmethod
def get_slots_for_day_handler(cls, facility_external_id, request_data):
request_data = SlotsForDayRequestSpec(**request_data)
user = User.objects.filter(external_id=request_data.resource).first()
if not user:
raise ValidationError("Resource does not exist")
user = get_object_or_404(User, external_id=request_data.user)
schedulable_resource_obj = SchedulableUserResource.objects.filter(
facility__external_id=facility_external_id,
resource=user,
user=user,
).first()
if not schedulable_resource_obj:
raise ValidationError("Resource is not schedulable")
Expand Down Expand Up @@ -194,9 +194,13 @@ def create_appointment_handler(cls, obj, request_data, user):

@action(detail=True, methods=["POST"])
def create_appointment(self, request, *args, **kwargs):
return self.create_appointment_handler(
self.get_object(), request.data, request.user
)
slot_obj = self.get_object()
facility = slot_obj.resource.facility
if not AuthorizationController.call(
"can_create_appointment", self.request.user, facility
):
raise PermissionDenied("You do not have permission to create appointments")
return self.create_appointment_handler(slot_obj, request.data, request.user)

@action(detail=False, methods=["POST"])
def availability_stats(self, request, *args, **kwargs):
Expand All @@ -206,10 +210,10 @@ def availability_stats(self, request, *args, **kwargs):
"""
request_data = AvailabilityStatsRequestSpec(**request.data)
# Fetch the entire schedule and calculate total slots available for each day
user = User.objects.filter(external_id=request_data.resource).first()
user = User.objects.filter(external_id=request_data.user).first()
if not user:
raise ValidationError("User does not exist")
resource = SchedulableUserResource.objects.filter(resource=user).first()
resource = SchedulableUserResource.objects.filter(user=user).first()
if not resource:
raise ValidationError("Resource is not schedulable")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


class AvailabilityExceptionFilters(FilterSet):
resource = UUIDFilter(field_name="resource__resource__external_id")
user = UUIDFilter(field_name="resource__user__external_id")


class AvailabilityExceptionsViewSet(EMRModelViewSet):
Expand Down
10 changes: 5 additions & 5 deletions care/emr/api/viewsets/scheduling/booking.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ class TokenBookingFilters(FilterSet):
status = CharFilter(field_name="status")
date = DateFilter(field_name="token_slot__start_datetime__date")
slot = UUIDFilter(field_name="token_slot__external_id")
resource = UUIDFilter(method="filter_by_resource")
user = UUIDFilter(method="filter_by_user")
patient = UUIDFilter(field_name="patient__external_id")

def filter_by_resource(self, queryset, name, value):
def filter_by_user(self, queryset, name, value):
if not value:
return queryset
resource = SchedulableUserResource.objects.filter(
resource__external_id=value
user__external_id=value
).first()
if not resource:
return queryset.none()
Expand Down Expand Up @@ -90,13 +90,13 @@ def get_queryset(self):
)

@action(detail=False, methods=["GET"])
def available_doctors(self, request, *args, **kwargs):
def available_users(self, request, *args, **kwargs):
facility = Facility.objects.get(external_id=self.kwargs["facility_external_id"])
facility_users = FacilityOrganizationUser.objects.filter(
organization__facility=facility,
user_id__in=SchedulableUserResource.objects.filter(
facility=facility
).values("resource_id"),
).values("user_id"),
)

return Response(
Expand Down
4 changes: 2 additions & 2 deletions care/emr/api/viewsets/scheduling/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


class ScheduleFilters(FilterSet):
resource = UUIDFilter(field_name="resource__resource__external_id")
user = UUIDFilter(field_name="resource__user__external_id")


class ScheduleViewSet(EMRModelViewSet):
Expand Down Expand Up @@ -51,7 +51,7 @@ def validate_data(self, instance, model_obj=None):
facility = self.get_facility_obj()
schedule_user = get_object_or_404(User, external_id=instance.user)
if not FacilityOrganizationUser.objects.filter(
user=schedule_user, facility=facility
user=schedule_user, organization__facility=facility
).exists():
raise ValidationError("Schedule User is not part of the facility")

Expand Down
7 changes: 6 additions & 1 deletion care/emr/api/viewsets/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
UserTypeRoleMapping,
UserUpdateSpec,
)
from care.security.authorization import AuthorizationController
from care.security.models import RoleModel
from care.users.api.serializers.user import UserImageUploadSerializer, UserSerializer
from care.users.models import User
Expand Down Expand Up @@ -72,7 +73,11 @@ def perform_create(self, instance):
def authorize_update(self, request_obj, model_instance):
if self.request.user.is_superuser:
return True
return request_obj.user == model_instance
return self.request.user.id == model_instance.id

def authorize_create(self, instance):
if not AuthorizationController.call("can_create_user", self.request.user):
raise PermissionDenied("You do not have permission to create Users")

def authorize_delete(self, instance):
return self.request.user.is_superuser
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.1.3 on 2025-01-03 10:23

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("emr", "0060_alter_medicationrequest_dosage_instruction"),
]

operations = [
migrations.RenameField(
model_name="schedulableuserresource",
old_name="resource",
new_name="user",
),
migrations.AlterField(
model_name="medicationrequest",
name="dosage_instruction",
field=models.JSONField(blank=True, default=list, null=True),
),
]
Loading

0 comments on commit 617b8ae

Please sign in to comment.