From 462b70b9ae7881ea20ef8d9e5c393e92683da85a Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Sat, 21 Dec 2024 00:15:26 +0530 Subject: [PATCH 01/10] fixes loading of lsg and ward data; fixes #2667 --- care/users/management/commands/load_lsg_data.py | 2 +- care/users/management/commands/load_ward_data.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/care/users/management/commands/load_lsg_data.py b/care/users/management/commands/load_lsg_data.py index 1398ce4f8c..e61b8634ef 100644 --- a/care/users/management/commands/load_lsg_data.py +++ b/care/users/management/commands/load_lsg_data.py @@ -89,7 +89,7 @@ def create_local_bodies(local_body_list): # Hence, those records can be ignored using the `ignore_conflicts` flag LocalBody.objects.bulk_create(local_body_objs, ignore_conflicts=True) - for counter, f in enumerate(sorted(Path.glob(f"{folder}/*.json"))): + for counter, f in enumerate(sorted(Path(folder).glob("*.json"))): with Path(f).open() as data_f: data = json.load(data_f) data.pop("wards", None) diff --git a/care/users/management/commands/load_ward_data.py b/care/users/management/commands/load_ward_data.py index 9b14dd2bba..9d472e533c 100644 --- a/care/users/management/commands/load_ward_data.py +++ b/care/users/management/commands/load_ward_data.py @@ -59,7 +59,7 @@ def get_local_body(lb): ), ).first() - for f in sorted(Path.glob(f"{folder}/*.json")): + for f in sorted(Path(folder).glob("*.json")): with Path(f).open() as data_f: data = json.load(data_f) wards = data.pop("wards", None) From da99364330950f60142ea6c001b436d47148c02d Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Sat, 21 Dec 2024 18:25:49 +0530 Subject: [PATCH 02/10] tokens availability stats API: fix infinite loop on edge cases and type issues --- .../api/viewsets/scheduling/availability.py | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/availability.py b/care/emr/api/viewsets/scheduling/availability.py index 683d4654e2..6faa85a88a 100644 --- a/care/emr/api/viewsets/scheduling/availability.py +++ b/care/emr/api/viewsets/scheduling/availability.py @@ -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 @@ -10,16 +10,11 @@ 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.resources.scheduling.schedule.spec import SlotTypeOptions from care.emr.resources.scheduling.slot.spec import ( TokenBookingRetrieveSpec, TokenSlotBaseSpec, @@ -309,8 +304,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: @@ -319,10 +314,10 @@ def calculate_slots( and exception["end_time"] >= start_time ): conflicting = True - if conflicting: - continue - slots += availability["tokens_per_slot"] - start_time += timedelta( - minutes=availability["slot_size_in_minutes"] - ) + if not conflicting: + slots += availability["tokens_per_slot"] + start_time = ( + datetime.datetime.combine(date.today(), start_time) + + timedelta(minutes=availability["slot_size_in_minutes"]) + ).time() return slots From 56c91e1a6ad90225a5eb4f82198cb9aac859d934 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Sun, 22 Dec 2024 18:08:51 +0530 Subject: [PATCH 03/10] simplify diff --- care/emr/api/viewsets/scheduling/availability.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/availability.py b/care/emr/api/viewsets/scheduling/availability.py index 6faa85a88a..f470e8704c 100644 --- a/care/emr/api/viewsets/scheduling/availability.py +++ b/care/emr/api/viewsets/scheduling/availability.py @@ -308,16 +308,17 @@ def calculate_slots( end_time = time.fromisoformat(available_slot["end_time"]) while start_time <= end_time: conflicting = False + start_time = ( + datetime.datetime.combine(date.today(), start_time) + + timedelta(minutes=availability["slot_size_in_minutes"]) + ).time() for exception in exceptions: if ( exception["start_time"] <= end_time and exception["end_time"] >= start_time ): conflicting = True - if not conflicting: - slots += availability["tokens_per_slot"] - 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"] return slots From 316a3bccabab6f428cf5c73d71c3f525749bce1c Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Mon, 23 Dec 2024 00:11:49 +0530 Subject: [PATCH 04/10] serialize resource user in list spec --- care/emr/api/otp_viewsets/slot.py | 7 ++----- care/emr/resources/scheduling/slot/spec.py | 19 ++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/care/emr/api/otp_viewsets/slot.py b/care/emr/api/otp_viewsets/slot.py index f1b0fa3c96..317b34b7df 100644 --- a/care/emr/api/otp_viewsets/slot.py +++ b/care/emr/api/otp_viewsets/slot.py @@ -3,10 +3,7 @@ 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, @@ -15,7 +12,7 @@ from care.emr.models import TokenBooking, TokenSlot from care.emr.resources.scheduling.slot.spec import ( TokenBookingReadSpec, - TokenBookingRetrieveSpec, TokenSlotBaseSpec, + TokenSlotBaseSpec, ) from care.facility.models import PatientRegistration from config.patient_otp_authentication import ( diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index 66afd33f2d..d2817abbf1 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -5,9 +5,7 @@ from care.emr.models import TokenBooking from care.emr.models.scheduling.booking import TokenSlot -from care.emr.models.scheduling.schedule import ( - Availability, -) +from care.emr.models.scheduling.schedule import Availability from care.emr.resources.base import EMRResource from care.emr.resources.patient.otp_based_flow import PatientOTPWriteSpec from care.emr.resources.user.spec import UserSpec @@ -73,6 +71,7 @@ class TokenBookingReadSpec(TokenBookingBaseSpec): booked_by: UserSpec status: str reason_for_visit: str + resource: dict = {} @classmethod def perform_extra_serialization(cls, mapping, obj): @@ -83,17 +82,11 @@ def perform_extra_serialization(cls, mapping, obj): mapping["patient"] = PatientOTPWriteSpec.serialize(obj.patient).model_dump( exclude=["meta"] ) - - -class TokenBookingRetrieveSpec(TokenBookingReadSpec): - id: UUID4 | None = None - - resource: dict = {} - - @classmethod - def perform_extra_serialization(cls, mapping, obj): - super().perform_extra_serialization(mapping, obj) if obj.token_slot.resource.resource_type == "user": mapping["resource"] = UserSpec.serialize( User.objects.get(id=obj.token_slot.resource.resource_id) ).model_dump(exclude=["meta"]) + + +class TokenBookingRetrieveSpec(TokenBookingReadSpec): + id: UUID4 | None = None From 5dca0f6947f69ebdd7274d37f6574c77465695f5 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Mon, 23 Dec 2024 00:18:33 +0530 Subject: [PATCH 05/10] fixes avatar not working --- care/emr/resources/user/spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/care/emr/resources/user/spec.py b/care/emr/resources/user/spec.py index e9915efe12..ed0e7eab69 100644 --- a/care/emr/resources/user/spec.py +++ b/care/emr/resources/user/spec.py @@ -11,7 +11,7 @@ class UserSpec(EMRResource): last_name: str user_type: str last_login: str - read_profile_picture_url: str + profile_picture_url: str @classmethod def perform_extra_serialization(cls, mapping, obj: User): From e6cf03099b694131ff2b2a035e537df2963c52a7 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Mon, 23 Dec 2024 11:59:31 +0530 Subject: [PATCH 06/10] fix issue with patient read spec --- care/emr/resources/scheduling/slot/spec.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index d2817abbf1..facb5d47ff 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -7,7 +7,7 @@ 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.patient.otp_based_flow import PatientOTPWriteSpec +from care.emr.resources.patient.otp_based_flow import PatientOTPReadSpec from care.emr.resources.user.spec import UserSpec from care.users.models import User @@ -66,7 +66,7 @@ class TokenBookingReadSpec(TokenBookingBaseSpec): id: UUID4 | None = None token_slot: TokenSlotBaseSpec - patient: PatientOTPWriteSpec + patient: PatientOTPReadSpec booked_on: datetime.datetime booked_by: UserSpec status: str @@ -79,7 +79,7 @@ def perform_extra_serialization(cls, mapping, obj): mapping["token_slot"] = TokenSlotBaseSpec.serialize(obj.token_slot).model_dump( exclude=["meta"] ) - mapping["patient"] = PatientOTPWriteSpec.serialize(obj.patient).model_dump( + mapping["patient"] = PatientOTPReadSpec.serialize(obj.patient).model_dump( exclude=["meta"] ) if obj.token_slot.resource.resource_type == "user": From b1cd22b9b6ba4424faea659332c9724514dd986c Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Mon, 23 Dec 2024 14:02:50 +0530 Subject: [PATCH 07/10] serialize location details in patient read spec --- care/emr/resources/patient/otp_based_flow.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/care/emr/resources/patient/otp_based_flow.py b/care/emr/resources/patient/otp_based_flow.py index 6d147cf183..78e4e773bb 100644 --- a/care/emr/resources/patient/otp_based_flow.py +++ b/care/emr/resources/patient/otp_based_flow.py @@ -27,12 +27,21 @@ class PatientOTPReadSpec(PatientOTPBaseSpec): phone_number: str emergency_phone_number: str address: str + pincode: int + state: str + district: str + local_body: str + ward: str date_of_birth: datetime.date year_of_birth: int @classmethod def perform_extra_serialization(cls, mapping, obj): mapping["id"] = obj.external_id + mapping["state"] = obj.state.name if obj.state else None + mapping["district"] = obj.district.name if obj.district else None + mapping["local_body"] = obj.local_body.name if obj.local_body else None + mapping["ward"] = obj.ward.name if obj.ward else None class PatientOTPWriteSpec(PatientOTPBaseSpec): From 870503a7551ea115b3eec7a25be270829e119f41 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 24 Dec 2024 08:56:11 +0530 Subject: [PATCH 08/10] adds slot filter --- care/emr/api/viewsets/scheduling/booking.py | 1 + 1 file changed, 1 insertion(+) diff --git a/care/emr/api/viewsets/scheduling/booking.py b/care/emr/api/viewsets/scheduling/booking.py index 32b2e70c7a..eb9f110548 100644 --- a/care/emr/api/viewsets/scheduling/booking.py +++ b/care/emr/api/viewsets/scheduling/booking.py @@ -22,6 +22,7 @@ class TokenBookingFilters(FilterSet): status = CharFilter(field_name="status") + slot = UUIDFilter(field_name="token_slot__external_id") patient = UUIDFilter(field_name="patient__external_id") From 058fd3c15c6ec0920836b5382ed2eb6652d2ee13 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 24 Dec 2024 21:33:58 +0530 Subject: [PATCH 09/10] Simplify schedulable resource relation for users --- care/emr/api/otp_viewsets/slot.py | 2 +- .../api/viewsets/scheduling/availability.py | 26 +- .../scheduling/availability_exceptions.py | 6 +- care/emr/api/viewsets/scheduling/booking.py | 8 +- care/emr/api/viewsets/scheduling/schedule.py | 8 +- ..._remove_tokenslot_availability_and_more.py | 103 +++++ ...resource_availabilityexception_and_more.py | 436 ++++++++++++++++++ care/emr/models/scheduling/booking.py | 4 +- care/emr/models/scheduling/schedule.py | 11 +- .../scheduling/availability_exception/spec.py | 25 +- .../emr/resources/scheduling/schedule/spec.py | 34 +- care/emr/resources/scheduling/slot/spec.py | 7 +- 12 files changed, 591 insertions(+), 79 deletions(-) create mode 100644 care/emr/migrations/0034_remove_tokenslot_availability_and_more.py create mode 100644 care/emr/migrations/0035_schedulableuserresource_availabilityexception_and_more.py diff --git a/care/emr/api/otp_viewsets/slot.py b/care/emr/api/otp_viewsets/slot.py index 317b34b7df..42cef4c5a4 100644 --- a/care/emr/api/otp_viewsets/slot.py +++ b/care/emr/api/otp_viewsets/slot.py @@ -9,7 +9,7 @@ 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, diff --git a/care/emr/api/viewsets/scheduling/availability.py b/care/emr/api/viewsets/scheduling/availability.py index f470e8704c..bf3b7c7788 100644 --- a/care/emr/api/viewsets/scheduling/availability.py +++ b/care/emr/api/viewsets/scheduling/availability.py @@ -13,7 +13,7 @@ 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.models.scheduling.schedule import Availability, SchedulableUserResource from care.emr.resources.scheduling.schedule.spec import SlotTypeOptions from care.emr.resources.scheduling.slot.spec import ( TokenBookingRetrieveSpec, @@ -26,7 +26,6 @@ class SlotsForDayRequestSpec(BaseModel): resource: UUID4 - resource_type: str = "user" day: datetime.date @@ -39,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): @@ -105,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") @@ -212,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, diff --git a/care/emr/api/viewsets/scheduling/availability_exceptions.py b/care/emr/api/viewsets/scheduling/availability_exceptions.py index c21b4dcd3f..0275ecb19c 100644 --- a/care/emr/api/viewsets/scheduling/availability_exceptions.py +++ b/care/emr/api/viewsets/scheduling/availability_exceptions.py @@ -34,11 +34,7 @@ def get_queryset(self): .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" - ): + if self.request.GET.get("resource"): user_obj = User.objects.filter( external_id=self.request.GET.get("resource") ).first() diff --git a/care/emr/api/viewsets/scheduling/booking.py b/care/emr/api/viewsets/scheduling/booking.py index eb9f110548..18a6f56c3c 100644 --- a/care/emr/api/viewsets/scheduling/booking.py +++ b/care/emr/api/viewsets/scheduling/booking.py @@ -10,7 +10,7 @@ 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, @@ -61,9 +61,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, ) diff --git a/care/emr/api/viewsets/scheduling/schedule.py b/care/emr/api/viewsets/scheduling/schedule.py index 1425bb2f1a..be99b66d54 100644 --- a/care/emr/api/viewsets/scheduling/schedule.py +++ b/care/emr/api/viewsets/scheduling/schedule.py @@ -43,14 +43,10 @@ def get_queryset(self): .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" - ): + if self.request.GET.get("resource"): 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) + queryset = queryset.filter(resource__resource=user_obj) return queryset diff --git a/care/emr/migrations/0034_remove_tokenslot_availability_and_more.py b/care/emr/migrations/0034_remove_tokenslot_availability_and_more.py new file mode 100644 index 0000000000..3da694b1c9 --- /dev/null +++ b/care/emr/migrations/0034_remove_tokenslot_availability_and_more.py @@ -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", + ), + ] diff --git a/care/emr/migrations/0035_schedulableuserresource_availabilityexception_and_more.py b/care/emr/migrations/0035_schedulableuserresource_availabilityexception_and_more.py new file mode 100644 index 0000000000..5210d5ff1c --- /dev/null +++ b/care/emr/migrations/0035_schedulableuserresource_availabilityexception_and_more.py @@ -0,0 +1,436 @@ +# Generated by Django 5.1.3 on 2024-12-24 15:48 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("emr", "0034_remove_tokenslot_availability_and_more"), + ("facility", "0475_merge_20241223_2352"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="SchedulableUserResource", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ("history", models.JSONField(default=dict)), + ("meta", models.JSONField(default=dict)), + ( + "created_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "facility", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="facility.facility", + ), + ), + ( + "resource", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="AvailabilityException", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ("history", models.JSONField(default=dict)), + ("meta", models.JSONField(default=dict)), + ("name", models.CharField(max_length=255)), + ("reason", models.TextField(blank=True, null=True)), + ("valid_from", models.DateField()), + ("valid_to", models.DateField()), + ("start_time", models.TimeField()), + ("end_time", models.TimeField()), + ( + "created_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "resource", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="emr.schedulableuserresource", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Schedule", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ("history", models.JSONField(default=dict)), + ("meta", models.JSONField(default=dict)), + ("name", models.CharField(max_length=255)), + ("valid_from", models.DateTimeField()), + ("valid_to", models.DateTimeField()), + ( + "created_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "resource", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="emr.schedulableuserresource", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Availability", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ("history", models.JSONField(default=dict)), + ("meta", models.JSONField(default=dict)), + ("name", models.CharField(max_length=255)), + ("slot_type", models.CharField()), + ("slot_size_in_minutes", models.IntegerField(default=0)), + ("tokens_per_slot", models.IntegerField(default=0)), + ("create_tokens", models.BooleanField(default=False)), + ("reason", models.TextField(blank=True, null=True)), + ("availability", models.JSONField(default=dict)), + ( + "created_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "schedule", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="emr.schedule" + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="TokenSlot", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ("history", models.JSONField(default=dict)), + ("meta", models.JSONField(default=dict)), + ("start_datetime", models.DateTimeField()), + ("end_datetime", models.DateTimeField()), + ("allocated", models.IntegerField(default=0)), + ( + "availability", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="emr.availability", + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "resource", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="emr.schedulableuserresource", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="TokenBooking", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ( + "created_date", + models.DateTimeField(auto_now_add=True, db_index=True, null=True), + ), + ( + "modified_date", + models.DateTimeField(auto_now=True, db_index=True, null=True), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ("history", models.JSONField(default=dict)), + ("meta", models.JSONField(default=dict)), + ("booked_on", models.DateTimeField(auto_now_add=True)), + ("status", models.CharField()), + ("reason_for_visit", models.TextField(blank=True, null=True)), + ( + "booked_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "patient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="facility.patientregistration", + ), + ), + ( + "updated_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_updated_by", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "token_slot", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="emr.tokenslot" + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/care/emr/models/scheduling/booking.py b/care/emr/models/scheduling/booking.py index 7c6a41f667..dea4ee0140 100644 --- a/care/emr/models/scheduling/booking.py +++ b/care/emr/models/scheduling/booking.py @@ -1,14 +1,14 @@ from django.db import models from care.emr.models import EMRBaseModel -from care.emr.models.scheduling.schedule import Availability, SchedulableResource +from care.emr.models.scheduling.schedule import Availability, SchedulableUserResource from care.facility.models import PatientRegistration from care.users.models import User class TokenSlot(EMRBaseModel): resource = models.ForeignKey( - SchedulableResource, on_delete=models.CASCADE, null=False, blank=False + SchedulableUserResource, on_delete=models.CASCADE, null=False, blank=False ) availability = models.ForeignKey( Availability, on_delete=models.CASCADE, null=True, blank=True diff --git a/care/emr/models/scheduling/schedule.py b/care/emr/models/scheduling/schedule.py index 24855ad2b8..88db96daaf 100644 --- a/care/emr/models/scheduling/schedule.py +++ b/care/emr/models/scheduling/schedule.py @@ -3,18 +3,17 @@ from care.emr.models import EMRBaseModel -class SchedulableResource(EMRBaseModel): +class SchedulableUserResource(EMRBaseModel): """A resource that can be scheduled for appointments.""" facility = models.ForeignKey("facility.Facility", on_delete=models.CASCADE) - resource_id = models.IntegerField() - resource_type = models.CharField(max_length=255) + resource = models.ForeignKey("users.User", on_delete=models.CASCADE) - # TODO : Index with resource_id and resource_type and facility + # TODO : Index with resource and facility class Schedule(EMRBaseModel): - resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE) + resource = models.ForeignKey(SchedulableUserResource, on_delete=models.CASCADE) name = models.CharField(max_length=255) valid_from = models.DateTimeField() valid_to = models.DateTimeField() @@ -32,7 +31,7 @@ class Availability(EMRBaseModel): class AvailabilityException(EMRBaseModel): - resource = models.ForeignKey(SchedulableResource, on_delete=models.CASCADE) + resource = models.ForeignKey(SchedulableUserResource, on_delete=models.CASCADE) name = models.CharField(max_length=255) reason = models.TextField(null=True, blank=True) valid_from = models.DateField(null=False, blank=False) diff --git a/care/emr/resources/scheduling/availability_exception/spec.py b/care/emr/resources/scheduling/availability_exception/spec.py index 34b446ca1d..65f4144023 100644 --- a/care/emr/resources/scheduling/availability_exception/spec.py +++ b/care/emr/resources/scheduling/availability_exception/spec.py @@ -6,9 +6,7 @@ from rest_framework.exceptions import ValidationError from care.emr.models import AvailabilityException -from care.emr.models.scheduling.schedule import ( - SchedulableResource, -) +from care.emr.models.scheduling.schedule import SchedulableUserResource from care.emr.resources.base import EMRResource from care.facility.models import Facility from care.users.models import User @@ -33,22 +31,19 @@ class AvailabilityExceptionBaseSpec(EMRResource): class AvailabilityExceptionWriteSpec(AvailabilityExceptionBaseSpec): facility: UUID4 | None = None resource: UUID4 - resource_type: ResourceTypeOptions = ResourceTypeOptions.user def perform_extra_deserialization(self, is_update, obj): if not is_update: resource = None - if self.resource_type == ResourceTypeOptions.user: - try: - user_resource = User.objects.get(external_id=self.resource) - resource = SchedulableResource.objects.get( - resource_id=user_resource.id, - resource_type="user", - facility=Facility.objects.get(external_id=self.facility), - ) - obj.resource = resource - except ObjectDoesNotExist as e: - raise ValidationError("Object does not exist") from e + try: + user_resource = User.objects.get(external_id=self.resource) + resource = SchedulableUserResource.objects.get( + resource=user_resource, + facility=Facility.objects.get(external_id=self.facility), + ) + obj.resource = resource + except ObjectDoesNotExist as e: + raise ValidationError("Object does not exist") from e class AvailabilityExceptionReadSpec(AvailabilityExceptionBaseSpec): diff --git a/care/emr/resources/scheduling/schedule/spec.py b/care/emr/resources/scheduling/schedule/spec.py index 67090b30eb..705060a10c 100644 --- a/care/emr/resources/scheduling/schedule/spec.py +++ b/care/emr/resources/scheduling/schedule/spec.py @@ -5,7 +5,7 @@ from care.emr.models.scheduling.schedule import ( Availability, - SchedulableResource, + SchedulableUserResource, Schedule, ) from care.emr.resources.base import EMRResource @@ -55,33 +55,28 @@ class ScheduleBaseSpec(EMRResource): class ScheduleWriteSpec(ScheduleBaseSpec): resource: UUID4 facility: UUID4 - resource_type: ResourceTypeOptions = ResourceTypeOptions.user name: str valid_from: datetime.datetime valid_to: datetime.datetime availabilities: list[AvailabilityBaseSpec] def perform_extra_deserialization(self, is_update, obj): - if not is_update: # noqa SIM102 - if self.resource_type == ResourceTypeOptions.user: - user = User.objects.filter(external_id=self.resource).first() - # TODO Validation that user is in given facility - if not user: - raise ValueError("User not found") - obj.facility = Facility.objects.get(external_id=self.facility) - - resource, _ = SchedulableResource.objects.get_or_create( - facility=obj.facility, - resource_type=ResourceTypeOptions.user.value, - resource_id=user.id, - ) - obj.resource = resource - obj.availabilities = self.availabilities + if not is_update: + user = User.objects.filter(external_id=self.resource).first() + # TODO Validation that user is in given facility + if not user: + raise ValueError("User not found") + obj.facility = Facility.objects.get(external_id=self.facility) + + resource, _ = SchedulableUserResource.objects.get_or_create( + facility=obj.facility, + resource=user, + ) + obj.resource = resource + obj.availabilities = self.availabilities class ScheduleReadSpec(ScheduleBaseSpec): - resource: UUID4 - resource_type: ResourceTypeOptions = ResourceTypeOptions.user name: str valid_from: datetime.datetime valid_to: datetime.datetime @@ -92,7 +87,6 @@ class ScheduleReadSpec(ScheduleBaseSpec): @classmethod def perform_extra_serialization(cls, mapping, obj): mapping["id"] = obj.external_id - mapping["resource"] = obj.resource.external_id if obj.created_by: mapping["created_by"] = UserSpec.serialize(obj.created_by) diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index facb5d47ff..e8bfdcea2a 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -82,10 +82,9 @@ def perform_extra_serialization(cls, mapping, obj): mapping["patient"] = PatientOTPReadSpec.serialize(obj.patient).model_dump( exclude=["meta"] ) - if obj.token_slot.resource.resource_type == "user": - mapping["resource"] = UserSpec.serialize( - User.objects.get(id=obj.token_slot.resource.resource_id) - ).model_dump(exclude=["meta"]) + mapping["resource"] = UserSpec.serialize( + User.objects.get(id=obj.token_slot.resource.resource_id) + ).model_dump(exclude=["meta"]) class TokenBookingRetrieveSpec(TokenBookingReadSpec): From 59c5cd3400d834e9fd936ae3f082981eda4e62f8 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Tue, 24 Dec 2024 21:55:43 +0530 Subject: [PATCH 10/10] suggestions based on review --- care/emr/api/viewsets/scheduling/availability.py | 12 ++++++------ .../viewsets/scheduling/availability_exceptions.py | 14 +++----------- care/emr/api/viewsets/scheduling/booking.py | 2 -- care/emr/api/viewsets/scheduling/schedule.py | 14 +++----------- care/emr/resources/scheduling/slot/spec.py | 4 ---- 5 files changed, 12 insertions(+), 34 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/availability.py b/care/emr/api/viewsets/scheduling/availability.py index bf3b7c7788..3a37d5e003 100644 --- a/care/emr/api/viewsets/scheduling/availability.py +++ b/care/emr/api/viewsets/scheduling/availability.py @@ -16,7 +16,7 @@ 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 @@ -191,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"]) @@ -302,16 +302,16 @@ def calculate_slots( end_time = time.fromisoformat(available_slot["end_time"]) while start_time <= end_time: conflicting = False - start_time = ( - datetime.datetime.combine(date.today(), start_time) - + timedelta(minutes=availability["slot_size_in_minutes"]) - ).time() for exception in exceptions: if ( exception["start_time"] <= end_time 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"] diff --git a/care/emr/api/viewsets/scheduling/availability_exceptions.py b/care/emr/api/viewsets/scheduling/availability_exceptions.py index 0275ecb19c..70abf30d44 100644 --- a/care/emr/api/viewsets/scheduling/availability_exceptions.py +++ b/care/emr/api/viewsets/scheduling/availability_exceptions.py @@ -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 @@ -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): @@ -27,17 +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"): - 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 diff --git a/care/emr/api/viewsets/scheduling/booking.py b/care/emr/api/viewsets/scheduling/booking.py index 18a6f56c3c..08dba5f1a1 100644 --- a/care/emr/api/viewsets/scheduling/booking.py +++ b/care/emr/api/viewsets/scheduling/booking.py @@ -13,7 +13,6 @@ 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 @@ -33,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] diff --git a/care/emr/api/viewsets/scheduling/schedule.py b/care/emr/api/viewsets/scheduling/schedule.py index be99b66d54..9506fc03bc 100644 --- a/care/emr/api/viewsets/scheduling/schedule.py +++ b/care/emr/api/viewsets/scheduling/schedule.py @@ -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 @@ -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): @@ -36,17 +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"): - user_obj = User.objects.filter( - external_id=self.request.GET.get("resource") - ).first() - if user_obj: - queryset = queryset.filter(resource__resource=user_obj) - return queryset diff --git a/care/emr/resources/scheduling/slot/spec.py b/care/emr/resources/scheduling/slot/spec.py index e8bfdcea2a..1c872e78d1 100644 --- a/care/emr/resources/scheduling/slot/spec.py +++ b/care/emr/resources/scheduling/slot/spec.py @@ -85,7 +85,3 @@ def perform_extra_serialization(cls, mapping, obj): mapping["resource"] = UserSpec.serialize( User.objects.get(id=obj.token_slot.resource.resource_id) ).model_dump(exclude=["meta"]) - - -class TokenBookingRetrieveSpec(TokenBookingReadSpec): - id: UUID4 | None = None