From 933db6ed220b0478ae8f554367537085e1f86d67 Mon Sep 17 00:00:00 2001 From: phenomenal Date: Sun, 29 Mar 2026 15:26:44 +0530 Subject: [PATCH 1/4] (feat)O3-5552: Refactor error message for future transition date validation --- .../queue/api/impl/QueueEntryServiceImpl.java | 7 ++++- .../QueueEntryTransitionRestController.java | 26 +++++++++---------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/org/openmrs/module/queue/api/impl/QueueEntryServiceImpl.java b/api/src/main/java/org/openmrs/module/queue/api/impl/QueueEntryServiceImpl.java index b0acb344..491336f3 100644 --- a/api/src/main/java/org/openmrs/module/queue/api/impl/QueueEntryServiceImpl.java +++ b/api/src/main/java/org/openmrs/module/queue/api/impl/QueueEntryServiceImpl.java @@ -116,7 +116,12 @@ public QueueEntry transitionQueueEntry(QueueEntryTransition queueEntryTransition if (currentState.getEndedAt() != null) { throw new IllegalStateException("Cannot transition a queue entry that has already ended"); } - + // Validate transition date is not in the future (with 1-minute tolerance for clock drift) + Date maxAllowedDate = new Date(System.currentTimeMillis() + 60000L); + if (queueEntryTransition.getTransitionDate() != null + && queueEntryTransition.getTransitionDate().after(maxAllowedDate)) { + throw new APIException("Transition date cannot be in the future"); + } // Capture the dateChanged for optimistic locking Date expectedDateChanged = currentState.getDateChanged(); diff --git a/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java b/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java index caa32c5e..77770e06 100644 --- a/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java +++ b/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java @@ -37,26 +37,26 @@ */ @Controller public class QueueEntryTransitionRestController extends BaseRestController { - + private final QueueServicesWrapper services; - + @Autowired public QueueEntryTransitionRestController(QueueServicesWrapper services) { this.services = services; } - + @RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/queue-entry/transition", method = { RequestMethod.PUT, - RequestMethod.POST }) + RequestMethod.POST }) @ResponseBody public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body) { QueueEntryTransition transition = new QueueEntryTransition(); - + // Queue Entry to Transition String queueEntryUuid = body.getQueueEntryToTransition(); QueueEntry queueEntry = services.getQueueEntryService().getQueueEntryByUuid(queueEntryUuid) - .orElseThrow(() -> new APIException("queueEntryToTransition not specified or found")); + .orElseThrow(() -> new APIException("queueEntryToTransition not specified or found")); transition.setQueueEntryToTransition(queueEntry); - + // Transition Date Date transitionDate = new Date(); if (body.getTransitionDate() != null) { @@ -66,7 +66,7 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body throw new APIException("Invalid transition date specified: " + body.getTransitionDate()); } transition.setTransitionDate(transitionDate); - + // Queue if (body.getNewQueue() != null) { Optional queueOptional = services.getQueueService().getQueueByUuid(body.getNewQueue()); @@ -75,7 +75,7 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body } transition.setNewQueue(queueOptional.get()); } - + // Status if (body.getNewStatus() != null) { Concept concept = services.getConcept(body.getNewStatus()); @@ -84,7 +84,7 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body } transition.setNewStatus(concept); } - + // Priority if (body.getNewPriority() != null) { Concept concept = services.getConcept(body.getNewPriority()); @@ -93,14 +93,14 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body } transition.setNewPriority(concept); } - + transition.setNewPriorityComment(body.getNewPriorityComment()); - + // Execute transition QueueEntry newQueueEntry = services.getQueueEntryService().transitionQueueEntry(transition); return ConversionUtil.convertToRepresentation(newQueueEntry, Representation.REF); } - + @RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/queue-entry/transition", method = RequestMethod.DELETE) @ResponseBody public Object undoTransition(@RequestBody UndoQueueEntryTransitionRequest body) { From 502154e1f63172e48c77f6f2889184d9e695678b Mon Sep 17 00:00:00 2001 From: phenomenal Date: Fri, 10 Apr 2026 22:33:23 +0530 Subject: [PATCH 2/4] clean extra changes of controller --- .../QueueEntryTransitionRestController.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java b/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java index 77770e06..a51e38cc 100644 --- a/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java +++ b/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java @@ -37,26 +37,26 @@ */ @Controller public class QueueEntryTransitionRestController extends BaseRestController { - + private final QueueServicesWrapper services; - + @Autowired public QueueEntryTransitionRestController(QueueServicesWrapper services) { this.services = services; } - + @RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/queue-entry/transition", method = { RequestMethod.PUT, - RequestMethod.POST }) + RequestMethod.POST }) @ResponseBody public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body) { QueueEntryTransition transition = new QueueEntryTransition(); - + // Queue Entry to Transition String queueEntryUuid = body.getQueueEntryToTransition(); QueueEntry queueEntry = services.getQueueEntryService().getQueueEntryByUuid(queueEntryUuid) - .orElseThrow(() -> new APIException("queueEntryToTransition not specified or found")); + .orElseThrow(() -> new APIException("queueEntryToTransition not specified or found")); transition.setQueueEntryToTransition(queueEntry); - + // Transition Date Date transitionDate = new Date(); if (body.getTransitionDate() != null) { @@ -66,7 +66,7 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body throw new APIException("Invalid transition date specified: " + body.getTransitionDate()); } transition.setTransitionDate(transitionDate); - + // Queue if (body.getNewQueue() != null) { Optional queueOptional = services.getQueueService().getQueueByUuid(body.getNewQueue()); @@ -75,7 +75,7 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body } transition.setNewQueue(queueOptional.get()); } - + // Status if (body.getNewStatus() != null) { Concept concept = services.getConcept(body.getNewStatus()); @@ -84,7 +84,7 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body } transition.setNewStatus(concept); } - + // Priority if (body.getNewPriority() != null) { Concept concept = services.getConcept(body.getNewPriority()); @@ -93,14 +93,14 @@ public Object transitionQueueEntry(@RequestBody QueueEntryTransitionRequest body } transition.setNewPriority(concept); } - + transition.setNewPriorityComment(body.getNewPriorityComment()); - + // Execute transition QueueEntry newQueueEntry = services.getQueueEntryService().transitionQueueEntry(transition); return ConversionUtil.convertToRepresentation(newQueueEntry, Representation.REF); } - + @RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/queue-entry/transition", method = RequestMethod.DELETE) @ResponseBody public Object undoTransition(@RequestBody UndoQueueEntryTransitionRequest body) { @@ -113,4 +113,4 @@ public Object undoTransition(@RequestBody UndoQueueEntryTransitionRequest body) throw new APIException("Invalid queue entry"); } } -} +} \ No newline at end of file From 9b170304b7ad38968dabe4621cb6c9c0e7275e05 Mon Sep 17 00:00:00 2001 From: Brijesh-shah <110579053+Brijesh-0106@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:34:20 +0530 Subject: [PATCH 3/4] Fix missing newline in QueueEntryTransitionRestController Add missing newline at end of file. --- .../module/queue/web/QueueEntryTransitionRestController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java b/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java index a51e38cc..caa32c5e 100644 --- a/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java +++ b/omod/src/main/java/org/openmrs/module/queue/web/QueueEntryTransitionRestController.java @@ -113,4 +113,4 @@ public Object undoTransition(@RequestBody UndoQueueEntryTransitionRequest body) throw new APIException("Invalid queue entry"); } } -} \ No newline at end of file +} From 57c21cde7518fb312169b4c0f10b6046ebabd9f5 Mon Sep 17 00:00:00 2001 From: phenomenal Date: Sat, 11 Apr 2026 10:06:36 +0530 Subject: [PATCH 4/4] Add tests for future transition date validation in QueueEntryService --- .../queue/api/QueueEntryServiceTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/api/src/test/java/org/openmrs/module/queue/api/QueueEntryServiceTest.java b/api/src/test/java/org/openmrs/module/queue/api/QueueEntryServiceTest.java index 28b4d6fe..ae7a6146 100644 --- a/api/src/test/java/org/openmrs/module/queue/api/QueueEntryServiceTest.java +++ b/api/src/test/java/org/openmrs/module/queue/api/QueueEntryServiceTest.java @@ -40,6 +40,7 @@ import org.openmrs.User; import org.openmrs.Visit; import org.openmrs.VisitAttributeType; +import org.openmrs.api.APIException; import org.openmrs.api.VisitService; import org.openmrs.api.context.Context; import org.openmrs.api.context.UserContext; @@ -470,4 +471,47 @@ public void shouldGenerateVisitQueueNumber() { assertThat(queueNumber, notNullValue()); assertThat(queueNumber, equalTo("CON-053")); } + + @Test(expected = APIException.class) + public void shouldThrowWhenTransitionDateIsInTheFuture() { + QueueEntry queueEntry = new QueueEntry(); + queueEntry.setQueueEntryId(1); + when(dao.get(1)).thenReturn(Optional.of(queueEntry)); + + QueueEntryTransition transition = new QueueEntryTransition(); + transition.setQueueEntryToTransition(queueEntry); + // Set transition date 2 minutes in the future — well beyond the 1-minute tolerance + transition.setTransitionDate(DateUtils.addMinutes(new Date(), 2)); + + queueEntryService.transitionQueueEntry(transition); + } + + @Test + public void shouldAllowTransitionDateWithinOneMinuteClockDriftTolerance() { + QueueEntry queueEntry = new QueueEntry(); + queueEntry.setQueueEntryId(1); + queueEntry.setQueue(new Queue()); + queueEntry.setPatient(new Patient()); + queueEntry.setStatus(new Concept()); + queueEntry.setPriority(new Concept()); + queueEntry.setStartedAt(DateUtils.addHours(new Date(), -1)); + when(dao.get(1)).thenReturn(Optional.of(queueEntry)); + when(dao.updateIfUnmodified(any(), any())).thenReturn(true); + when(dao.createOrUpdate(any())).thenAnswer(invocation -> { + QueueEntry entry = invocation.getArgument(0); + if (entry.getId() == null) { + entry.setQueueEntryId(2); + } + return entry; + }); + + QueueEntryTransition transition = new QueueEntryTransition(); + transition.setQueueEntryToTransition(queueEntry); + // Set transition date 30 seconds in future — within the 1-minute tolerance + transition.setTransitionDate(DateUtils.addMinutes(new Date(), 1)); + + // Should NOT throw — 30 seconds is within the allowed clock drift window + QueueEntry result = queueEntryService.transitionQueueEntry(transition); + assertThat(result, notNullValue()); + } }