From b7eb13e27c48a3b96d222a87aec988178ca51be7 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 15:26:42 +0530 Subject: [PATCH 1/4] Add reschedule APIs --- .../api/viewsets/scheduling/availability.py | 5 ++- care/emr/api/viewsets/scheduling/booking.py | 44 ++++++++++++++++++- .../0007_tokenbooking_previous_booking.py | 24 ++++++++++ care/emr/models/scheduling/booking.py | 3 ++ care/emr/resources/scheduling/slot/spec.py | 4 +- 5 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 care/emr/migrations/0007_tokenbooking_previous_booking.py diff --git a/care/emr/api/viewsets/scheduling/availability.py b/care/emr/api/viewsets/scheduling/availability.py index 192d8898b2..2dbf06c102 100644 --- a/care/emr/api/viewsets/scheduling/availability.py +++ b/care/emr/api/viewsets/scheduling/availability.py @@ -78,7 +78,9 @@ def convert_availability_to_slots(availabilities): return slots -def lock_create_appointment(token_slot, patient, created_by, reason_for_visit): +def lock_create_appointment( + token_slot, patient, created_by, reason_for_visit, previous_booking=None +): with Lock(f"booking:resource:{token_slot.resource.id}"), transaction.atomic(): if token_slot.allocated >= token_slot.availability.tokens_per_slot: raise ValidationError("Slot is already full") @@ -90,6 +92,7 @@ def lock_create_appointment(token_slot, patient, created_by, reason_for_visit): booked_by=created_by, reason_for_visit=reason_for_visit, status="booked", + previous_booking=previous_booking, ) diff --git a/care/emr/api/viewsets/scheduling/booking.py b/care/emr/api/viewsets/scheduling/booking.py index 010962be21..9952b6428c 100644 --- a/care/emr/api/viewsets/scheduling/booking.py +++ b/care/emr/api/viewsets/scheduling/booking.py @@ -3,7 +3,7 @@ from django.db import transaction from django_filters import CharFilter, DateFromToRangeFilter, FilterSet, UUIDFilter from django_filters.rest_framework import DjangoFilterBackend -from pydantic import BaseModel +from pydantic import UUID4, BaseModel from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework.generics import get_object_or_404 @@ -15,6 +15,8 @@ EMRRetrieveMixin, EMRUpdateMixin, ) +from care.emr.api.viewsets.scheduling import lock_create_appointment +from care.emr.models import TokenSlot from care.emr.models.scheduling import SchedulableUserResource, TokenBooking from care.emr.resources.scheduling.slot.spec import ( CANCELLED_STATUS_CHOICES, @@ -29,10 +31,16 @@ class CancelBookingSpec(BaseModel): reason: Literal[ - BookingStatusChoices.cancelled, BookingStatusChoices.entered_in_error + BookingStatusChoices.cancelled, + BookingStatusChoices.entered_in_error, + BookingStatusChoices.rescheduled, ] +class RescheduleBookingSpec(BaseModel): + new_slot: UUID4 + + class TokenBookingFilters(FilterSet): status = CharFilter(field_name="status") date = DateFromToRangeFilter(field_name="token_slot__start_datetime__date") @@ -117,6 +125,38 @@ def cancel(self, request, *args, **kwargs): self.authorize_update({}, instance) return self.cancel_appointment_handler(instance, request.data, request.user) + @action(detail=True, methods=["POST"]) + def reschedule(self, request, *args, **kwargs): + request_data = RescheduleBookingSpec(**request.data) + existing_booking = self.get_object() + facility = self.get_facility_obj() + self.authorize_update({}, existing_booking) + if not AuthorizationController.call( + "can_create_appointment", self.request.user, facility + ): + raise PermissionDenied("You do not have permission to create appointments") + new_slot = get_object_or_404( + TokenSlot, + external_id=request_data.new_slot, + resource=existing_booking.token_slot.resource, + ) + with transaction.atomic(): + self.cancel_appointment_handler( + existing_booking, + {"reason": BookingStatusChoices.rescheduled}, + request.user, + ) + appointment = lock_create_appointment( + new_slot, + existing_booking.patient, + request.user, + existing_booking.reason_for_visit, + previous_booking=existing_booking, + ) + return Response( + TokenBookingReadSpec.serialize(appointment).model_dump(exclude=["meta"]) + ) + @action(detail=False, methods=["GET"]) def available_users(self, request, *args, **kwargs): facility = Facility.objects.get(external_id=self.kwargs["facility_external_id"]) diff --git a/care/emr/migrations/0007_tokenbooking_previous_booking.py b/care/emr/migrations/0007_tokenbooking_previous_booking.py new file mode 100644 index 0000000000..511e56952c --- /dev/null +++ b/care/emr/migrations/0007_tokenbooking_previous_booking.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.3 on 2025-01-14 09:37 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("emr", "0006_alter_patient_blood_group"), + ] + + operations = [ + migrations.AddField( + model_name="tokenbooking", + name="previous_booking", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="emr.tokenbooking", + ), + ), + ] diff --git a/care/emr/models/scheduling/booking.py b/care/emr/models/scheduling/booking.py index ff7d6f0e2e..c2583d758e 100644 --- a/care/emr/models/scheduling/booking.py +++ b/care/emr/models/scheduling/booking.py @@ -32,3 +32,6 @@ class TokenBooking(EMRBaseModel): booked_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) status = models.CharField() reason_for_visit = models.TextField(null=True, blank=True) + previous_booking = models.ForeignKey( + "TokenBooking", on_delete=models.SET_NULL, null=True, blank=True + ) diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index 25df6b5f90..13c07fff7f 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -52,17 +52,19 @@ class BookingStatusChoices(str, Enum): checked_in = "checked_in" waitlist = "waitlist" in_consultation = "in_consultation" + rescheduled = "rescheduled" CANCELLED_STATUS_CHOICES = [ BookingStatusChoices.entered_in_error.value, BookingStatusChoices.cancelled.value, + BookingStatusChoices.rescheduled.value, ] class TokenBookingBaseSpec(EMRResource): __model__ = TokenBooking - __exclude__ = ["token_slot", "patient"] + __exclude__ = ["token_slot", "patient", "previous_booking"] class TokenBookingWriteSpec(TokenBookingBaseSpec): From db4553ffae7594acc6dc1f9c62bd63905e769b3e Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 15:48:41 +0530 Subject: [PATCH 2/4] serialize facility info --- care/emr/resources/scheduling/slot/spec.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index 13c07fff7f..693bab1d8b 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -8,9 +8,11 @@ from care.emr.models.scheduling.booking import TokenSlot from care.emr.models.scheduling.schedule import Availability from care.emr.resources.base import EMRResource +from care.emr.resources.facility.spec import FacilityBareMinimumSpec from care.emr.resources.patient.otp_based_flow import PatientOTPReadSpec from care.emr.resources.user.spec import UserSpec from care.users.models import User +from care.facility.models import Facility class AvailabilityforTokenSpec(EMRResource): @@ -85,6 +87,7 @@ class TokenBookingReadSpec(TokenBookingBaseSpec): status: str reason_for_visit: str user: dict = {} + facility: dict = {} @classmethod def perform_extra_serialization(cls, mapping, obj): @@ -98,3 +101,6 @@ def perform_extra_serialization(cls, mapping, obj): mapping["user"] = UserSpec.serialize( User.objects.get(id=obj.token_slot.resource.user_id) ).model_dump(exclude=["meta"]) + mapping["facility"] = FacilityBareMinimumSpec.serialize( + Facility.objects.get(id=obj.token_slot.resource.facility_id) + ).model_dump(exclude=["meta"]) From 7fefed88095e26dfc74be1f2f2d17c92bd4a15f2 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 16:04:07 +0530 Subject: [PATCH 3/4] fix lint --- care/emr/resources/scheduling/slot/spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index 693bab1d8b..fa235d4c5c 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -11,8 +11,8 @@ from care.emr.resources.facility.spec import FacilityBareMinimumSpec from care.emr.resources.patient.otp_based_flow import PatientOTPReadSpec from care.emr.resources.user.spec import UserSpec -from care.users.models import User from care.facility.models import Facility +from care.users.models import User class AvailabilityforTokenSpec(EMRResource): From 523a7456c2c3a020cda02c705befaeeda99896af Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 14 Jan 2025 20:35:04 +0530 Subject: [PATCH 4/4] remove back reference --- .../api/viewsets/scheduling/availability.py | 5 +--- care/emr/api/viewsets/scheduling/booking.py | 1 - .../0007_tokenbooking_previous_booking.py | 24 ------------------- care/emr/models/scheduling/booking.py | 3 --- care/emr/resources/scheduling/slot/spec.py | 2 +- 5 files changed, 2 insertions(+), 33 deletions(-) delete mode 100644 care/emr/migrations/0007_tokenbooking_previous_booking.py diff --git a/care/emr/api/viewsets/scheduling/availability.py b/care/emr/api/viewsets/scheduling/availability.py index 2dbf06c102..192d8898b2 100644 --- a/care/emr/api/viewsets/scheduling/availability.py +++ b/care/emr/api/viewsets/scheduling/availability.py @@ -78,9 +78,7 @@ def convert_availability_to_slots(availabilities): return slots -def lock_create_appointment( - token_slot, patient, created_by, reason_for_visit, previous_booking=None -): +def lock_create_appointment(token_slot, patient, created_by, reason_for_visit): with Lock(f"booking:resource:{token_slot.resource.id}"), transaction.atomic(): if token_slot.allocated >= token_slot.availability.tokens_per_slot: raise ValidationError("Slot is already full") @@ -92,7 +90,6 @@ def lock_create_appointment( booked_by=created_by, reason_for_visit=reason_for_visit, status="booked", - previous_booking=previous_booking, ) diff --git a/care/emr/api/viewsets/scheduling/booking.py b/care/emr/api/viewsets/scheduling/booking.py index 9952b6428c..7486603c01 100644 --- a/care/emr/api/viewsets/scheduling/booking.py +++ b/care/emr/api/viewsets/scheduling/booking.py @@ -151,7 +151,6 @@ def reschedule(self, request, *args, **kwargs): existing_booking.patient, request.user, existing_booking.reason_for_visit, - previous_booking=existing_booking, ) return Response( TokenBookingReadSpec.serialize(appointment).model_dump(exclude=["meta"]) diff --git a/care/emr/migrations/0007_tokenbooking_previous_booking.py b/care/emr/migrations/0007_tokenbooking_previous_booking.py deleted file mode 100644 index 511e56952c..0000000000 --- a/care/emr/migrations/0007_tokenbooking_previous_booking.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.1.3 on 2025-01-14 09:37 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("emr", "0006_alter_patient_blood_group"), - ] - - operations = [ - migrations.AddField( - model_name="tokenbooking", - name="previous_booking", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="emr.tokenbooking", - ), - ), - ] diff --git a/care/emr/models/scheduling/booking.py b/care/emr/models/scheduling/booking.py index c2583d758e..ff7d6f0e2e 100644 --- a/care/emr/models/scheduling/booking.py +++ b/care/emr/models/scheduling/booking.py @@ -32,6 +32,3 @@ class TokenBooking(EMRBaseModel): booked_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) status = models.CharField() reason_for_visit = models.TextField(null=True, blank=True) - previous_booking = models.ForeignKey( - "TokenBooking", on_delete=models.SET_NULL, null=True, blank=True - ) diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index fa235d4c5c..cb40bb3461 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -66,7 +66,7 @@ class BookingStatusChoices(str, Enum): class TokenBookingBaseSpec(EMRResource): __model__ = TokenBooking - __exclude__ = ["token_slot", "patient", "previous_booking"] + __exclude__ = ["token_slot", "patient"] class TokenBookingWriteSpec(TokenBookingBaseSpec):