From 637e5ce02db86d809f51a156dfeeb26c7d1645e8 Mon Sep 17 00:00:00 2001 From: nandkishorr Date: Fri, 10 Jul 2026 15:20:32 +0530 Subject: [PATCH 1/7] fix:added proper token assigning --- care/emr/api/viewsets/scheduling/token_queue.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/token_queue.py b/care/emr/api/viewsets/scheduling/token_queue.py index 97b673ed31..aed409d72a 100644 --- a/care/emr/api/viewsets/scheduling/token_queue.py +++ b/care/emr/api/viewsets/scheduling/token_queue.py @@ -232,8 +232,12 @@ def set_next_token_to_subqueue(self, request, *args, **kwargs): ).order_by("created_date") if category: tokens_qs = tokens_qs.filter(category=category) - if tokens_qs.exists(): - next_token = tokens_qs.first() + tokens_in_waiting_qs = tokens_qs.filter(sub_queue__isnull=True) + tokens_in_calling_qs = tokens_qs.filter(sub_queue=sub_queue) + if tokens_in_calling_qs.exists(): + next_token = tokens_in_calling_qs.first() + elif tokens_in_waiting_qs.exists(): + next_token = tokens_in_waiting_qs.first() else: raise ValidationError("No tokens found") sub_queue.current_token = next_token From 9d383a71628d19958ca4d4e2b998f7258feab729 Mon Sep 17 00:00:00 2001 From: nandkishorr Date: Fri, 10 Jul 2026 15:33:28 +0530 Subject: [PATCH 2/7] fix:added proper token validation and clear current token --- care/emr/api/viewsets/scheduling/token.py | 53 +++++++++++++++++------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/token.py b/care/emr/api/viewsets/scheduling/token.py index d2c5e5f8e2..ccec4cc011 100644 --- a/care/emr/api/viewsets/scheduling/token.py +++ b/care/emr/api/viewsets/scheduling/token.py @@ -93,25 +93,52 @@ def validate_data(self, instance, model_obj=None): if existing_current: raise ValidationError("Sub Queue already has a current token") + if ( + instance.sub_queue + and instance.status == TokenStatusOptions.IN_PROGRESS.value + ): + new_current = get_object_or_404( + TokenSubQueue, + external_id=instance.sub_queue, + ) + if new_current.current_token and new_current.current_token != model_obj: + raise ValidationError("Sub Queue already has a current token") + return super().validate_data(instance, model_obj) def perform_update(self, instance): + obj = self.get_object() + if ( + instance.sub_queue + and obj.sub_queue != instance.sub_queue + and instance.status == TokenStatusOptions.IN_PROGRESS.value + ): + raise ValidationError( + "Use set_next endpoint to change the sub queue of a token" + ) if instance.sub_queue and instance.sub_queue.facility != instance.facility: raise ValidationError("Sub Queue and Queue are not in the same facility") with transaction.atomic(): - obj = self.get_object() - if obj.sub_queue and obj.sub_queue != instance.sub_queue: - if ( - instance.sub_queue - and obj.sub_queue.resource != instance.sub_queue.resource - ): - raise ValidationError( - "Sub Queue and Queue are not in the same resource" - ) - # Clear current token if the sub queue is changed - if obj.sub_queue.current_token == obj: - obj.sub_queue.current_token = None - obj.sub_queue.save(update_fields=["current_token", "modified_date"]) + if ( + obj.sub_queue + and obj.sub_queue != instance.sub_queue + and instance.sub_queue + and obj.sub_queue.resource != instance.sub_queue.resource + ): + raise ValidationError( + "Sub Queue and Queue are not in the same resource" + ) + # Clear current token if the sub queue is changed + if ( + obj.sub_queue + and obj.sub_queue.current_token == obj + and ( + obj.sub_queue != instance.sub_queue + or instance.status != TokenStatusOptions.IN_PROGRESS.value + ) + ): + obj.sub_queue.current_token = None + obj.sub_queue.save(update_fields=["current_token", "modified_date"]) super().perform_update(instance) def perform_destroy(self, instance): From 14ac7aeccced6bf8058ae4eb257665848b5e69ea Mon Sep 17 00:00:00 2001 From: nandkishorr Date: Fri, 10 Jul 2026 16:47:38 +0530 Subject: [PATCH 3/7] greptile review and fix tests --- care/emr/api/viewsets/scheduling/token.py | 61 +++++++++---------- .../api/viewsets/scheduling/token_queue.py | 7 +-- care/emr/tests/test_token_api.py | 8 +-- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/token.py b/care/emr/api/viewsets/scheduling/token.py index ccec4cc011..ba40364a31 100644 --- a/care/emr/api/viewsets/scheduling/token.py +++ b/care/emr/api/viewsets/scheduling/token.py @@ -81,44 +81,43 @@ def perform_create(self, instance): super().perform_create(instance) def validate_data(self, instance, model_obj=None): - if ( - model_obj - and instance.sub_queue - and model_obj.sub_queue - and instance.sub_queue != model_obj.sub_queue.external_id - ): - existing_current = TokenSubQueue.objects.filter( - current_token=model_obj - ).exists() - if existing_current: - raise ValidationError("Sub Queue already has a current token") - - if ( - instance.sub_queue - and instance.status == TokenStatusOptions.IN_PROGRESS.value - ): - new_current = get_object_or_404( - TokenSubQueue, - external_id=instance.sub_queue, - ) - if new_current.current_token and new_current.current_token != model_obj: - raise ValidationError("Sub Queue already has a current token") + if model_obj and instance.sub_queue: + if ( + model_obj.sub_queue + and instance.sub_queue != model_obj.sub_queue.external_id + ): + existing_current = TokenSubQueue.objects.filter( + current_token=model_obj + ).exists() + if existing_current: + raise ValidationError("Sub Queue already has a current token") + + if ( + instance.sub_queue + and instance.status == TokenStatusOptions.IN_PROGRESS.value + ): + new_current = get_object_or_404( + TokenSubQueue, + external_id=instance.sub_queue, + ) + if new_current.current_token and new_current.current_token != model_obj: + raise ValidationError("Sub Queue already has a current token") return super().validate_data(instance, model_obj) def perform_update(self, instance): - obj = self.get_object() - if ( - instance.sub_queue - and obj.sub_queue != instance.sub_queue - and instance.status == TokenStatusOptions.IN_PROGRESS.value - ): - raise ValidationError( - "Use set_next endpoint to change the sub queue of a token" - ) if instance.sub_queue and instance.sub_queue.facility != instance.facility: raise ValidationError("Sub Queue and Queue are not in the same facility") with transaction.atomic(): + obj = self.get_object() + if ( + instance.sub_queue + and obj.sub_queue != instance.sub_queue + and instance.status == TokenStatusOptions.IN_PROGRESS.value + ): + raise ValidationError( + "Use set_next endpoint to change the sub queue of a token" + ) if ( obj.sub_queue and obj.sub_queue != instance.sub_queue diff --git a/care/emr/api/viewsets/scheduling/token_queue.py b/care/emr/api/viewsets/scheduling/token_queue.py index aed409d72a..740bebebbb 100644 --- a/care/emr/api/viewsets/scheduling/token_queue.py +++ b/care/emr/api/viewsets/scheduling/token_queue.py @@ -234,11 +234,8 @@ def set_next_token_to_subqueue(self, request, *args, **kwargs): tokens_qs = tokens_qs.filter(category=category) tokens_in_waiting_qs = tokens_qs.filter(sub_queue__isnull=True) tokens_in_calling_qs = tokens_qs.filter(sub_queue=sub_queue) - if tokens_in_calling_qs.exists(): - next_token = tokens_in_calling_qs.first() - elif tokens_in_waiting_qs.exists(): - next_token = tokens_in_waiting_qs.first() - else: + next_token = tokens_in_calling_qs.first() or tokens_in_waiting_qs.first() + if next_token is None: raise ValidationError("No tokens found") sub_queue.current_token = next_token sub_queue.save() diff --git a/care/emr/tests/test_token_api.py b/care/emr/tests/test_token_api.py index 66d4661fb4..5b6dd920a0 100644 --- a/care/emr/tests/test_token_api.py +++ b/care/emr/tests/test_token_api.py @@ -275,7 +275,7 @@ def test_update_token_as_superuser(self): name="Sub Queue 1", ) token_data = { - "status": TokenStatusOptions.IN_PROGRESS, + "status": TokenStatusOptions.CREATED, "note": "Token is in progress", "sub_queue": subqueue.external_id, } @@ -289,7 +289,7 @@ def test_update_token_as_superuser(self): format="json", ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["status"], TokenStatusOptions.IN_PROGRESS.value) + self.assertEqual(response.data["status"], TokenStatusOptions.CREATED.value) def test_update_token_as_user_with_permission(self): """Test updating a token as a user with permission.""" @@ -311,7 +311,7 @@ def test_update_token_as_user_with_permission(self): name="Sub Queue 1", ) token_data = { - "status": TokenStatusOptions.IN_PROGRESS, + "status": TokenStatusOptions.CREATED, "note": "Token is in progress", "sub_queue": subqueue.external_id, } @@ -325,7 +325,7 @@ def test_update_token_as_user_with_permission(self): format="json", ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["status"], TokenStatusOptions.IN_PROGRESS.value) + self.assertEqual(response.data["status"], TokenStatusOptions.CREATED.value) def test_update_token_as_user_without_permission(self): """Test updating a token as a user without permission.""" From 140b0b1f1afa64c7557a25eaeff3099b14d002af Mon Sep 17 00:00:00 2001 From: nandkishorr Date: Fri, 10 Jul 2026 18:29:31 +0530 Subject: [PATCH 4/7] cleanup --- care/emr/api/viewsets/scheduling/token.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/token.py b/care/emr/api/viewsets/scheduling/token.py index 645eb2f04f..611d40483f 100644 --- a/care/emr/api/viewsets/scheduling/token.py +++ b/care/emr/api/viewsets/scheduling/token.py @@ -91,17 +91,11 @@ def validate_data(self, instance, model_obj=None): ).exists() if existing_current: raise ValidationError("Sub Queue already has a current token") - if ( instance.sub_queue and instance.status == TokenStatusOptions.IN_PROGRESS.value ): - new_current = get_object_or_404( - TokenSubQueue, - external_id=instance.sub_queue, - ) - if new_current.current_token and new_current.current_token != model_obj: - raise ValidationError("Sub Queue already has a current token") + raise ValidationError("Use set_next endpoint to assign a token.") return super().validate_data(instance, model_obj) @@ -110,14 +104,6 @@ def perform_update(self, instance): raise ValidationError("Sub Queue and Queue are not in the same facility") with transaction.atomic(): obj = self.get_object() - if ( - instance.sub_queue - and obj.sub_queue != instance.sub_queue - and instance.status == TokenStatusOptions.IN_PROGRESS.value - ): - raise ValidationError( - "Use set_next endpoint to change the sub queue of a token" - ) if ( obj.sub_queue and obj.sub_queue != instance.sub_queue @@ -207,6 +193,8 @@ def get_queryset(self): @action(detail=True, methods=["POST"]) def set_next(self, request, *args, **kwargs): obj = self.get_object() + if obj.status != TokenStatusOptions.CREATED.value: + raise ValidationError("Token in serving state cannot be set next") request_obj = SetCurrentTokenRequest(**request.data) queue = obj.queue self.authorize_update(None, obj) @@ -216,8 +204,11 @@ def set_next(self, request, *args, **kwargs): external_id=request_obj.sub_queue, resource=queue.resource, ) + if sub_queue.current_token and sub_queue.current_token != obj: + raise ValidationError("Sub Queue already has a current token") sub_queue.current_token = obj sub_queue.save() obj.status = TokenStatusOptions.IN_PROGRESS.value + obj.sub_queue = sub_queue obj.save() return Response(self.get_retrieve_pydantic_model().serialize(obj).to_json()) From b21fa4a1e6fd9998a026d98c4292ffeef2380141 Mon Sep 17 00:00:00 2001 From: nandkishorr Date: Fri, 10 Jul 2026 18:58:13 +0530 Subject: [PATCH 5/7] added lock --- care/emr/api/viewsets/scheduling/token.py | 12 ++++++------ care/emr/tests/test_token_api.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/token.py b/care/emr/api/viewsets/scheduling/token.py index 611d40483f..3f264ae036 100644 --- a/care/emr/api/viewsets/scheduling/token.py +++ b/care/emr/api/viewsets/scheduling/token.py @@ -198,12 +198,12 @@ def set_next(self, request, *args, **kwargs): request_obj = SetCurrentTokenRequest(**request.data) queue = obj.queue self.authorize_update(None, obj) - with transaction.atomic(): - sub_queue = get_object_or_404( - TokenSubQueue, - external_id=request_obj.sub_queue, - resource=queue.resource, - ) + sub_queue = get_object_or_404( + TokenSubQueue, + external_id=request_obj.sub_queue, + resource=queue.resource, + ) + with Lock(f"token:set_next:{sub_queue.id}"), transaction.atomic(): if sub_queue.current_token and sub_queue.current_token != obj: raise ValidationError("Sub Queue already has a current token") sub_queue.current_token = obj diff --git a/care/emr/tests/test_token_api.py b/care/emr/tests/test_token_api.py index f94111f753..f32a4c1c30 100644 --- a/care/emr/tests/test_token_api.py +++ b/care/emr/tests/test_token_api.py @@ -342,7 +342,7 @@ def test_update_token_as_user_without_permission(self): name="Sub Queue 1", ) token_data = { - "status": TokenStatusOptions.IN_PROGRESS, + "status": TokenStatusOptions.CREATED, "note": "Token is in progress", "sub_queue": subqueue.external_id, } From 421ab2a22d4549750af714deb8098e4409135c2d Mon Sep 17 00:00:00 2001 From: nandkishorr Date: Mon, 13 Jul 2026 17:36:18 +0530 Subject: [PATCH 6/7] fix:refactoring and cleanup --- care/emr/api/viewsets/scheduling/token.py | 19 +++++++------- .../api/viewsets/scheduling/token_queue.py | 10 ++++---- .../viewsets/scheduling/token_sub_queue.py | 25 ++++++++++++++++++- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/token.py b/care/emr/api/viewsets/scheduling/token.py index 3f264ae036..ebdc8d5fd7 100644 --- a/care/emr/api/viewsets/scheduling/token.py +++ b/care/emr/api/viewsets/scheduling/token.py @@ -119,7 +119,10 @@ def perform_update(self, instance): and obj.sub_queue.current_token == obj and ( obj.sub_queue != instance.sub_queue - or instance.status != TokenStatusOptions.IN_PROGRESS.value + or ( + obj.status == TokenStatusOptions.IN_PROGRESS.value + and instance.status != TokenStatusOptions.IN_PROGRESS.value + ) ) ): obj.sub_queue.current_token = None @@ -198,14 +201,12 @@ def set_next(self, request, *args, **kwargs): request_obj = SetCurrentTokenRequest(**request.data) queue = obj.queue self.authorize_update(None, obj) - sub_queue = get_object_or_404( - TokenSubQueue, - external_id=request_obj.sub_queue, - resource=queue.resource, - ) - with Lock(f"token:set_next:{sub_queue.id}"), transaction.atomic(): - if sub_queue.current_token and sub_queue.current_token != obj: - raise ValidationError("Sub Queue already has a current token") + with transaction.atomic(): + sub_queue = get_object_or_404( + TokenSubQueue, + external_id=request_obj.sub_queue, + resource=queue.resource, + ) sub_queue.current_token = obj sub_queue.save() obj.status = TokenStatusOptions.IN_PROGRESS.value diff --git a/care/emr/api/viewsets/scheduling/token_queue.py b/care/emr/api/viewsets/scheduling/token_queue.py index 740bebebbb..dd192cf913 100644 --- a/care/emr/api/viewsets/scheduling/token_queue.py +++ b/care/emr/api/viewsets/scheduling/token_queue.py @@ -228,14 +228,14 @@ def set_next_token_to_subqueue(self, request, *args, **kwargs): ) with Lock(f"queue:next_token:{obj.id}"), transaction.atomic(): tokens_qs = Token.objects.filter( - queue=obj, status__in=[TokenStatusOptions.CREATED.value] + queue=obj, + status__in=[TokenStatusOptions.CREATED.value], + sub_queue__isnull=True, ).order_by("created_date") if category: tokens_qs = tokens_qs.filter(category=category) - tokens_in_waiting_qs = tokens_qs.filter(sub_queue__isnull=True) - tokens_in_calling_qs = tokens_qs.filter(sub_queue=sub_queue) - next_token = tokens_in_calling_qs.first() or tokens_in_waiting_qs.first() - if next_token is None: + next_token = tokens_qs.first() + if not next_token: raise ValidationError("No tokens found") sub_queue.current_token = next_token sub_queue.save() diff --git a/care/emr/api/viewsets/scheduling/token_sub_queue.py b/care/emr/api/viewsets/scheduling/token_sub_queue.py index 69f2cfb5d5..5191112c63 100644 --- a/care/emr/api/viewsets/scheduling/token_sub_queue.py +++ b/care/emr/api/viewsets/scheduling/token_sub_queue.py @@ -1,7 +1,9 @@ from django.db import transaction from django_filters import CharFilter, FilterSet from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied, ValidationError +from rest_framework.response import Response from care.emr.api.viewsets.base import EMRModelViewSet from care.emr.api.viewsets.scheduling.schedule import ( @@ -9,7 +11,8 @@ get_schedulable_resource, validate_resource, ) -from care.emr.models.scheduling.token import TokenSubQueue +from care.emr.models.scheduling.token import Token, TokenSubQueue +from care.emr.resources.scheduling.token.spec import TokenReadSpec, TokenStatusOptions from care.emr.resources.scheduling.token_sub_queue.spec import ( TokenSubQueueBaseSpec, TokenSubQueueCreateSpec, @@ -17,6 +20,7 @@ ) from care.facility.models import Facility from care.security.authorization.base import AuthorizationController +from care.utils.lock import Lock from care.utils.shortcuts import get_object_or_404 @@ -124,3 +128,22 @@ def get_queryset(self): self.can_read_resource_token(resource) queryset = queryset.filter(resource=resource) return queryset + + @action(detail=True, methods=["POST"]) + def set_next_token(self, request, *args, **kwargs): + obj = self.get_object() + self.authorize_update(None, obj) + + with Lock(f"sub_queue:next_token:{obj.id}"), transaction.atomic(): + tokens_qs = Token.objects.filter( + sub_queue=obj, status__in=[TokenStatusOptions.CREATED.value] + ).order_by("created_date") + next_token = tokens_qs.first() + if not next_token: + raise ValidationError("No tokens found") + obj.current_token = next_token + obj.save() + next_token.status = TokenStatusOptions.IN_PROGRESS.value + next_token.sub_queue = obj + next_token.save() + return Response(TokenReadSpec.serialize(next_token).to_json()) From e3aa7a73f5dcabd72ac9ff96dde4e999abcf423f Mon Sep 17 00:00:00 2001 From: nandkishorr Date: Tue, 14 Jul 2026 12:50:36 +0530 Subject: [PATCH 7/7] clean up --- care/emr/api/viewsets/scheduling/token.py | 16 ++++++++-------- care/emr/api/viewsets/scheduling/token_queue.py | 4 ++-- .../api/viewsets/scheduling/token_sub_queue.py | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/care/emr/api/viewsets/scheduling/token.py b/care/emr/api/viewsets/scheduling/token.py index ebdc8d5fd7..38fbe14226 100644 --- a/care/emr/api/viewsets/scheduling/token.py +++ b/care/emr/api/viewsets/scheduling/token.py @@ -201,15 +201,15 @@ def set_next(self, request, *args, **kwargs): request_obj = SetCurrentTokenRequest(**request.data) queue = obj.queue self.authorize_update(None, obj) - with transaction.atomic(): - sub_queue = get_object_or_404( - TokenSubQueue, - external_id=request_obj.sub_queue, - resource=queue.resource, - ) + sub_queue = get_object_or_404( + TokenSubQueue, + external_id=request_obj.sub_queue, + resource=queue.resource, + ) + with Lock(f"sub_queue:next_token:{sub_queue.id}"), transaction.atomic(): sub_queue.current_token = obj - sub_queue.save() + sub_queue.save(update_fields=["current_token", "modified_date"]) obj.status = TokenStatusOptions.IN_PROGRESS.value obj.sub_queue = sub_queue - obj.save() + obj.save(update_fields=["status", "sub_queue", "modified_date"]) return Response(self.get_retrieve_pydantic_model().serialize(obj).to_json()) diff --git a/care/emr/api/viewsets/scheduling/token_queue.py b/care/emr/api/viewsets/scheduling/token_queue.py index dd192cf913..18db74d138 100644 --- a/care/emr/api/viewsets/scheduling/token_queue.py +++ b/care/emr/api/viewsets/scheduling/token_queue.py @@ -238,10 +238,10 @@ def set_next_token_to_subqueue(self, request, *args, **kwargs): if not next_token: raise ValidationError("No tokens found") sub_queue.current_token = next_token - sub_queue.save() + sub_queue.save(update_fields=["current_token", "modified_date"]) next_token.status = TokenStatusOptions.IN_PROGRESS.value next_token.sub_queue = sub_queue - next_token.save() + next_token.save(update_fields=["status", "sub_queue", "modified_date"]) return Response(TokenReadSpec.serialize(next_token).to_json()) @action(detail=True, methods=["GET"]) diff --git a/care/emr/api/viewsets/scheduling/token_sub_queue.py b/care/emr/api/viewsets/scheduling/token_sub_queue.py index 5191112c63..da390b20ad 100644 --- a/care/emr/api/viewsets/scheduling/token_sub_queue.py +++ b/care/emr/api/viewsets/scheduling/token_sub_queue.py @@ -142,8 +142,8 @@ def set_next_token(self, request, *args, **kwargs): if not next_token: raise ValidationError("No tokens found") obj.current_token = next_token - obj.save() + obj.save(update_fields=["current_token", "modified_date"]) next_token.status = TokenStatusOptions.IN_PROGRESS.value next_token.sub_queue = obj - next_token.save() + next_token.save(update_fields=["status", "sub_queue", "modified_date"]) return Response(TokenReadSpec.serialize(next_token).to_json())