Skip to content

Commit

Permalink
Merge pull request #2675 from ohcnetwork/rithviknishad/scheduling
Browse files Browse the repository at this point in the history
Scheduling and Appointments Enhancements
  • Loading branch information
vigneshhari authored Dec 24, 2024
2 parents a9e19f8 + 59c5cd3 commit c6ae260
Show file tree
Hide file tree
Showing 16 changed files with 624 additions and 138 deletions.
7 changes: 2 additions & 5 deletions care/emr/api/otp_viewsets/slot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response

from care.emr.api.viewsets.base import (
EMRBaseViewSet,
EMRRetrieveMixin,
)
from care.emr.api.viewsets.base import EMRBaseViewSet, EMRRetrieveMixin
from care.emr.api.viewsets.scheduling import (
AppointmentBookingSpec,
SlotsForDayRequestSpec,
SlotViewSet,
)
from care.emr.models import TokenBooking, TokenSlot
from care.emr.models.scheduling import TokenBooking, TokenSlot
from care.emr.resources.scheduling.slot.spec import (
TokenBookingReadSpec,
TokenSlotBaseSpec,
Expand Down
52 changes: 21 additions & 31 deletions care/emr/api/viewsets/scheduling/availability.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from datetime import timedelta
from datetime import time, timedelta

from dateutil.parser import parse
from django.db import transaction
Expand All @@ -10,18 +10,13 @@
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response

from care.emr.api.viewsets.base import (
EMRBaseViewSet,
EMRRetrieveMixin,
)
from care.emr.api.viewsets.base import EMRBaseViewSet, EMRRetrieveMixin
from care.emr.models import AvailabilityException, Schedule, TokenBooking
from care.emr.models.scheduling.booking import TokenSlot
from care.emr.models.scheduling.schedule import Availability, SchedulableResource
from care.emr.resources.scheduling.schedule.spec import (
SlotTypeOptions,
)
from care.emr.models.scheduling.schedule import Availability, SchedulableUserResource
from care.emr.resources.scheduling.schedule.spec import SlotTypeOptions
from care.emr.resources.scheduling.slot.spec import (
TokenBookingRetrieveSpec,
TokenBookingReadSpec,
TokenSlotBaseSpec,
)
from care.facility.models import PatientRegistration
Expand All @@ -31,7 +26,6 @@

class SlotsForDayRequestSpec(BaseModel):
resource: UUID4
resource_type: str = "user"
day: datetime.date


Expand All @@ -44,7 +38,6 @@ class AvailabilityStatsRequestSpec(BaseModel):
from_date: datetime.date
to_date: datetime.date
resource: UUID4
resource_type: str = "user"

@model_validator(mode="after")
def validate_period(self):
Expand Down Expand Up @@ -110,15 +103,13 @@ def get_slots_for_day(self, request, *args, **kwargs):

@classmethod
def get_slots_for_day_handler(cls, facility_external_id, request_data):
facility = facility_external_id
request_data = SlotsForDayRequestSpec(**request_data)
user = User.objects.filter(external_id=request_data.resource).first()
if not user:
raise ValidationError("Resource does not exist")
schedulable_resource_obj = SchedulableResource.objects.filter(
facility__external_id=facility,
resource_id=user.id,
resource_type=request_data.resource_type,
schedulable_resource_obj = SchedulableUserResource.objects.filter(
facility__external_id=facility_external_id,
resource=user,
).first()
if not schedulable_resource_obj:
raise ValidationError("Resource is not schedulable")
Expand Down Expand Up @@ -200,7 +191,7 @@ def create_appointment_handler(cls, obj, request_data, user):
obj, patient, user, request_data.reason_for_visit
)
return Response(
TokenBookingRetrieveSpec.serialize(appointment).model_dump(exclude=["meta"])
TokenBookingReadSpec.serialize(appointment).model_dump(exclude=["meta"])
)

@action(detail=True, methods=["POST"])
Expand All @@ -217,14 +208,12 @@ def availability_stats(self, request, *args, **kwargs):
"""
request_data = AvailabilityStatsRequestSpec(**request.data)
# Fetch the entire schedule and calculate total slots available for each day
resource = None
if request_data.resource_type == "user": # TODO make this a utility
user = User.objects.filter(external_id=request_data.resource).first()
if not user:
raise ValidationError("User does not exist")
resource = SchedulableResource.objects.filter(resource_id=user.id).first()
if not resource:
raise ValidationError("Resource is not schedulable")
user = User.objects.filter(external_id=request_data.resource).first()
if not user:
raise ValidationError("User does not exist")
resource = SchedulableUserResource.objects.filter(resource=user).first()
if not resource:
raise ValidationError("Resource is not schedulable")

schedules = Schedule.objects.filter(
valid_from__lte=request_data.to_date,
Expand Down Expand Up @@ -309,8 +298,8 @@ def calculate_slots(
for available_slot in availability["availability"]:
if available_slot["day_of_week"] != day_of_week:
continue
start_time = parse(available_slot["start_time"])
end_time = parse(available_slot["end_time"])
start_time = time.fromisoformat(available_slot["start_time"])
end_time = time.fromisoformat(available_slot["end_time"])
while start_time <= end_time:
conflicting = False
for exception in exceptions:
Expand All @@ -319,10 +308,11 @@ def calculate_slots(
and exception["end_time"] >= start_time
):
conflicting = True
start_time = (
datetime.datetime.combine(date.today(), start_time)
+ timedelta(minutes=availability["slot_size_in_minutes"])
).time()
if conflicting:
continue
slots += availability["tokens_per_slot"]
start_time += timedelta(
minutes=availability["slot_size_in_minutes"]
)
return slots
18 changes: 3 additions & 15 deletions care/emr/api/viewsets/scheduling/availability_exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django_filters import FilterSet
from django_filters import FilterSet, UUIDFilter
from django_filters.rest_framework import DjangoFilterBackend

from care.emr.api.viewsets.base import EMRModelViewSet
Expand All @@ -7,11 +7,10 @@
AvailabilityExceptionReadSpec,
AvailabilityExceptionWriteSpec,
)
from care.users.models import User


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


class AvailabilityExceptionsViewSet(EMRModelViewSet):
Expand All @@ -27,21 +26,10 @@ def clean_create_data(self, request_data):
return request_data

def get_queryset(self):
queryset = (
return (
super()
.get_queryset()
.filter(resource__facility__external_id=self.kwargs["facility_external_id"])
.select_related("resource", "created_by", "updated_by")
.order_by("-modified_date")
)
if (
self.request.GET.get("resource")
and self.request.GET.get("resource_type")
and self.request.GET.get("resource_type") == "user"
):
user_obj = User.objects.filter(
external_id=self.request.GET.get("resource")
).first()
if user_obj:
queryset = queryset.filter(resource__resource_id=user_obj.id)
return queryset
11 changes: 5 additions & 6 deletions care/emr/api/viewsets/scheduling/booking.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
EMRRetrieveMixin,
EMRUpdateMixin,
)
from care.emr.models import SchedulableResource, TokenBooking
from care.emr.models.scheduling import SchedulableUserResource, TokenBooking
from care.emr.resources.scheduling.slot.spec import (
TokenBookingReadSpec,
TokenBookingRetrieveSpec,
TokenBookingUpdateSpec,
)
from care.emr.resources.user.spec import UserSpec
Expand All @@ -22,6 +21,7 @@

class TokenBookingFilters(FilterSet):
status = CharFilter(field_name="status")
slot = UUIDFilter(field_name="token_slot__external_id")
patient = UUIDFilter(field_name="patient__external_id")


Expand All @@ -32,7 +32,6 @@ class TokenBookingViewSet(
pydantic_model = TokenBookingReadSpec
pydantic_read_model = TokenBookingReadSpec
pydantic_update_model = TokenBookingUpdateSpec
pydantic_retrieve_model = TokenBookingRetrieveSpec

filterset_class = TokenBookingFilters
filter_backends = [DjangoFilterBackend]
Expand Down Expand Up @@ -60,9 +59,9 @@ def get_queryset(self):
def available_doctors(self, request, *args, **kwargs):
facility = Facility.objects.get(external_id=self.kwargs["facility_external_id"])
facility_users = FacilityUser.objects.filter(
user_id__in=SchedulableResource.objects.filter(facility=facility).values(
"resource_id"
),
user_id__in=SchedulableUserResource.objects.filter(
facility=facility
).values("resource_id"),
facility=facility,
)

Expand Down
18 changes: 3 additions & 15 deletions care/emr/api/viewsets/scheduling/schedule.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db import transaction
from django_filters import FilterSet
from django_filters import FilterSet, UUIDFilter
from django_filters.rest_framework import DjangoFilterBackend

from care.emr.api.viewsets.base import EMRModelViewSet
Expand All @@ -8,11 +8,10 @@
ScheduleReadSpec,
ScheduleWriteSpec,
)
from care.users.models import User


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


class ScheduleViewSet(EMRModelViewSet):
Expand All @@ -36,21 +35,10 @@ def clean_create_data(self, request_data):
return request_data

def get_queryset(self):
queryset = (
return (
super()
.get_queryset()
.filter(resource__facility__external_id=self.kwargs["facility_external_id"])
.select_related("resource", "created_by", "updated_by")
.order_by("-modified_date")
)
if (
self.request.GET.get("resource")
and self.request.GET.get("resource_type")
and self.request.GET.get("resource_type") == "user"
):
user_obj = User.objects.filter(
external_id=self.request.GET.get("resource")
).first()
if user_obj:
queryset = queryset.filter(resource__resource_id=user_obj.id)
return queryset
103 changes: 103 additions & 0 deletions care/emr/migrations/0034_remove_tokenslot_availability_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Generated by Django 5.1.3 on 2024-12-24 13:55

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("emr", "0033_organization"),
]

operations = [
migrations.RemoveField(
model_name="tokenslot",
name="availability",
),
migrations.RemoveField(
model_name="availabilityexception",
name="created_by",
),
migrations.RemoveField(
model_name="availabilityexception",
name="resource",
),
migrations.RemoveField(
model_name="availabilityexception",
name="updated_by",
),
migrations.RemoveField(
model_name="schedulableresource",
name="created_by",
),
migrations.RemoveField(
model_name="schedulableresource",
name="facility",
),
migrations.RemoveField(
model_name="schedulableresource",
name="updated_by",
),
migrations.RemoveField(
model_name="schedule",
name="resource",
),
migrations.RemoveField(
model_name="tokenslot",
name="resource",
),
migrations.RemoveField(
model_name="schedule",
name="created_by",
),
migrations.RemoveField(
model_name="schedule",
name="updated_by",
),
migrations.RemoveField(
model_name="tokenbooking",
name="booked_by",
),
migrations.RemoveField(
model_name="tokenbooking",
name="created_by",
),
migrations.RemoveField(
model_name="tokenbooking",
name="patient",
),
migrations.RemoveField(
model_name="tokenbooking",
name="token_slot",
),
migrations.RemoveField(
model_name="tokenbooking",
name="updated_by",
),
migrations.RemoveField(
model_name="tokenslot",
name="created_by",
),
migrations.RemoveField(
model_name="tokenslot",
name="updated_by",
),
migrations.DeleteModel(
name="Availability",
),
migrations.DeleteModel(
name="AvailabilityException",
),
migrations.DeleteModel(
name="SchedulableResource",
),
migrations.DeleteModel(
name="Schedule",
),
migrations.DeleteModel(
name="TokenBooking",
),
migrations.DeleteModel(
name="TokenSlot",
),
]
Loading

0 comments on commit c6ae260

Please sign in to comment.