From 8f5957824c212b904243abb42048070e7488dc52 Mon Sep 17 00:00:00 2001 From: Baris Ozturkmen Date: Sat, 11 Jul 2026 09:18:31 +0100 Subject: [PATCH 1/2] updated snapshot version for team branch --- listing-command/listing-command-api/pom.xml | 2 +- listing-command/listing-command-handler/pom.xml | 2 +- listing-command/pom.xml | 2 +- listing-common/pom.xml | 2 +- listing-domain/listing-domain-aggregate/pom.xml | 2 +- listing-domain/listing-domain-common/pom.xml | 2 +- listing-domain/listing-domain-event/pom.xml | 2 +- listing-domain/listing-domain-xhibit/pom.xml | 2 +- listing-domain/pom.xml | 2 +- listing-event-sources/pom.xml | 2 +- listing-event/listing-event-listener/pom.xml | 2 +- listing-event/listing-event-processor/pom.xml | 2 +- listing-event/pom.xml | 2 +- listing-healthchecks/pom.xml | 2 +- listing-integration-test-persistence/pom.xml | 2 +- listing-integration-test/pom.xml | 2 +- listing-json/pom.xml | 2 +- listing-performance-test/pom.xml | 2 +- listing-query/listing-query-api/pom.xml | 2 +- listing-query/listing-query-view/pom.xml | 2 +- listing-query/pom.xml | 2 +- listing-service/pom.xml | 2 +- listing-viewstore/listing-viewstore-liquibase/pom.xml | 2 +- listing-viewstore/listing-viewstore-persistence/pom.xml | 2 +- listing-viewstore/pom.xml | 2 +- pom.xml | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/listing-command/listing-command-api/pom.xml b/listing-command/listing-command-api/pom.xml index 92b9f91e1..fbcf6471c 100644 --- a/listing-command/listing-command-api/pom.xml +++ b/listing-command/listing-command-api/pom.xml @@ -4,7 +4,7 @@ listing-command uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-command/listing-command-handler/pom.xml b/listing-command/listing-command-handler/pom.xml index 31b6987c0..d93cbf343 100644 --- a/listing-command/listing-command-handler/pom.xml +++ b/listing-command/listing-command-handler/pom.xml @@ -3,7 +3,7 @@ listing-command uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-command/pom.xml b/listing-command/pom.xml index 86f034bb8..5afc966ce 100644 --- a/listing-command/pom.xml +++ b/listing-command/pom.xml @@ -3,7 +3,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-common/pom.xml b/listing-common/pom.xml index 1bb1ffe37..0a38b191b 100644 --- a/listing-common/pom.xml +++ b/listing-common/pom.xml @@ -3,7 +3,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-domain/listing-domain-aggregate/pom.xml b/listing-domain/listing-domain-aggregate/pom.xml index 590c55d9a..463757ffd 100644 --- a/listing-domain/listing-domain-aggregate/pom.xml +++ b/listing-domain/listing-domain-aggregate/pom.xml @@ -3,7 +3,7 @@ listing-domain uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-domain/listing-domain-common/pom.xml b/listing-domain/listing-domain-common/pom.xml index ca5785304..db93d423c 100644 --- a/listing-domain/listing-domain-common/pom.xml +++ b/listing-domain/listing-domain-common/pom.xml @@ -3,7 +3,7 @@ listing-domain uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-domain/listing-domain-event/pom.xml b/listing-domain/listing-domain-event/pom.xml index adc552d3b..d9c256809 100644 --- a/listing-domain/listing-domain-event/pom.xml +++ b/listing-domain/listing-domain-event/pom.xml @@ -3,7 +3,7 @@ listing-domain uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-domain/listing-domain-xhibit/pom.xml b/listing-domain/listing-domain-xhibit/pom.xml index 475e3211a..f436d337f 100644 --- a/listing-domain/listing-domain-xhibit/pom.xml +++ b/listing-domain/listing-domain-xhibit/pom.xml @@ -3,7 +3,7 @@ listing-domain uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-domain/pom.xml b/listing-domain/pom.xml index 6c94b5d29..125775b5e 100644 --- a/listing-domain/pom.xml +++ b/listing-domain/pom.xml @@ -4,7 +4,7 @@ uk.gov.moj.cpp.listing listing-parent - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT listing-domain diff --git a/listing-event-sources/pom.xml b/listing-event-sources/pom.xml index c1f9d7bd3..25617059c 100644 --- a/listing-event-sources/pom.xml +++ b/listing-event-sources/pom.xml @@ -3,7 +3,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-event/listing-event-listener/pom.xml b/listing-event/listing-event-listener/pom.xml index 1feae7533..2f5190511 100644 --- a/listing-event/listing-event-listener/pom.xml +++ b/listing-event/listing-event-listener/pom.xml @@ -3,7 +3,7 @@ listing-event uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-event/listing-event-processor/pom.xml b/listing-event/listing-event-processor/pom.xml index 344964b4d..76443b77e 100644 --- a/listing-event/listing-event-processor/pom.xml +++ b/listing-event/listing-event-processor/pom.xml @@ -3,7 +3,7 @@ listing-event uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-event/pom.xml b/listing-event/pom.xml index 84cd9fe45..01f674566 100644 --- a/listing-event/pom.xml +++ b/listing-event/pom.xml @@ -3,7 +3,7 @@ uk.gov.moj.cpp.listing listing-parent - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-healthchecks/pom.xml b/listing-healthchecks/pom.xml index d1479d8f0..19b0fa8c9 100644 --- a/listing-healthchecks/pom.xml +++ b/listing-healthchecks/pom.xml @@ -3,7 +3,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-integration-test-persistence/pom.xml b/listing-integration-test-persistence/pom.xml index 8da13e002..fa82ac3f8 100644 --- a/listing-integration-test-persistence/pom.xml +++ b/listing-integration-test-persistence/pom.xml @@ -3,7 +3,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-integration-test/pom.xml b/listing-integration-test/pom.xml index d04d17a7b..174cea21a 100644 --- a/listing-integration-test/pom.xml +++ b/listing-integration-test/pom.xml @@ -3,7 +3,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-json/pom.xml b/listing-json/pom.xml index 9c07332c7..b30da4709 100644 --- a/listing-json/pom.xml +++ b/listing-json/pom.xml @@ -4,7 +4,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT listing-json diff --git a/listing-performance-test/pom.xml b/listing-performance-test/pom.xml index 297368b50..a6d26967f 100644 --- a/listing-performance-test/pom.xml +++ b/listing-performance-test/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.listing listing-parent - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT listing-performance-test diff --git a/listing-query/listing-query-api/pom.xml b/listing-query/listing-query-api/pom.xml index 25d271e48..80ce55e3a 100644 --- a/listing-query/listing-query-api/pom.xml +++ b/listing-query/listing-query-api/pom.xml @@ -4,7 +4,7 @@ uk.gov.moj.cpp.listing listing-query - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT listing-query-api diff --git a/listing-query/listing-query-view/pom.xml b/listing-query/listing-query-view/pom.xml index f84a92207..2ca65059e 100644 --- a/listing-query/listing-query-view/pom.xml +++ b/listing-query/listing-query-view/pom.xml @@ -4,7 +4,7 @@ uk.gov.moj.cpp.listing listing-query - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT listing-query-view diff --git a/listing-query/pom.xml b/listing-query/pom.xml index 96c560ae7..71c50e391 100644 --- a/listing-query/pom.xml +++ b/listing-query/pom.xml @@ -3,7 +3,7 @@ uk.gov.moj.cpp.listing listing-parent - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-service/pom.xml b/listing-service/pom.xml index 17f0a6aa5..6ccd8e3f6 100644 --- a/listing-service/pom.xml +++ b/listing-service/pom.xml @@ -3,7 +3,7 @@ uk.gov.moj.cpp.listing listing-parent - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-viewstore/listing-viewstore-liquibase/pom.xml b/listing-viewstore/listing-viewstore-liquibase/pom.xml index 4ea6c5082..b784ea4e7 100644 --- a/listing-viewstore/listing-viewstore-liquibase/pom.xml +++ b/listing-viewstore/listing-viewstore-liquibase/pom.xml @@ -3,7 +3,7 @@ listing-viewstore uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-viewstore/listing-viewstore-persistence/pom.xml b/listing-viewstore/listing-viewstore-persistence/pom.xml index 9d06cd6bb..5061967ef 100644 --- a/listing-viewstore/listing-viewstore-persistence/pom.xml +++ b/listing-viewstore/listing-viewstore-persistence/pom.xml @@ -3,7 +3,7 @@ uk.gov.moj.cpp.listing listing-viewstore - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/listing-viewstore/pom.xml b/listing-viewstore/pom.xml index ef4048aa0..8b770e583 100644 --- a/listing-viewstore/pom.xml +++ b/listing-viewstore/pom.xml @@ -3,7 +3,7 @@ listing-parent uk.gov.moj.cpp.listing - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 614f909bc..b326fa02e 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ uk.gov.moj.cpp.listing listing-parent - 17.104.4-SNAPSHOT + 17.104.4-MIG-SNAPSHOT pom Listing Context Microservice Parent From 096e10565e7527db319c02bf37c2f94bd2971a8e Mon Sep 17 00:00:00 2001 From: Baris Ozturkmen Date: Sat, 11 Jul 2026 11:20:56 +0100 Subject: [PATCH 2/2] Add migrate-crown-hearings-to-courtschedules command endpoint (SPRDT-1151) Bulk-assigns courtScheduleId to CROWN hearing days/non-default days during court-schedule migration, reusing the existing update-hearing-day-court-schedule merge path (only courtScheduleId changes; all other attributes preserved). Adds command API/handler, CrownHearingMigratedToCourtschedule event + listener, DRL (system-user only), schemas/examples, and integration test. Adds a domain aggregate test asserting nonDefaultDays field preservation. --- .../command/api/ListingCommandApi.java | 7 ++ .../api/accesscontrol/listing-command-api.drl | 9 ++ ...rate-crown-hearings-to-courtschedules.json | 30 ++++++ ...rate-crown-hearings-to-courtschedules.json | 18 ++++ .../src/raml/listing-command-api.raml | 19 ++++ .../command/api/ListingAccessControlTest.java | 21 ++++ .../command/api/ListingCommandApiTest.java | 16 +++ .../handler/ListingCommandHandler.java | 28 ++++- ...rate-crown-hearings-to-courtschedules.json | 30 ++++++ ...rate-crown-hearings-to-courtschedules.json | 18 ++++ .../listing-command-handler.messaging.raml | 4 + .../handler/ListingCommandHandlerTest.java | 28 +++++ .../cpp/listing/domain/aggregate/Hearing.java | 18 +++- .../aggregate/HearingAggregateTest.java | 102 ++++++++++++++++++ .../HearingDaysUpdateEventListener.java | 15 ++- .../HearingDaysUpdateEventListenerTest.java | 40 +++++++ ...own-hearing-migrated-to-courtschedule.json | 9 ++ ...own-hearing-migrated-to-courtschedule.json | 21 ++++ .../src/yaml/subscriptions-descriptor.yaml | 3 + ...igrateCrownHearingsToCourtSchedulesIT.java | 97 +++++++++++++++++ .../src/test/resources/endpoint.properties | 1 + .../json/schema/migrate-crown-hearing.json | 22 ++++ 22 files changed, 548 insertions(+), 8 deletions(-) create mode 100644 listing-command/listing-command-api/src/raml/json/listing.migrate-crown-hearings-to-courtschedules.json create mode 100644 listing-command/listing-command-api/src/raml/json/schema/listing.migrate-crown-hearings-to-courtschedules.json create mode 100644 listing-command/listing-command-handler/src/raml/json/listing.command.migrate-crown-hearings-to-courtschedules.json create mode 100644 listing-command/listing-command-handler/src/raml/json/schema/listing.command.migrate-crown-hearings-to-courtschedules.json create mode 100644 listing-event/listing-event-listener/src/yaml/json/listing.events.crown-hearing-migrated-to-courtschedule.json create mode 100644 listing-event/listing-event-listener/src/yaml/json/schema/listing.events.crown-hearing-migrated-to-courtschedule.json create mode 100644 listing-integration-test/src/test/java/uk/gov/moj/cpp/listing/it/MigrateCrownHearingsToCourtSchedulesIT.java create mode 100644 listing-json/src/main/resources/json/schema/migrate-crown-hearing.json diff --git a/listing-command/listing-command-api/src/main/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApi.java b/listing-command/listing-command-api/src/main/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApi.java index 5ca66cedd..52e04c4ec 100644 --- a/listing-command/listing-command-api/src/main/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApi.java +++ b/listing-command/listing-command-api/src/main/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApi.java @@ -78,6 +78,7 @@ public class ListingCommandApi { private static final String LISTING_COMMAND_DELETE_HEARING = "listing.command.delete-hearing"; private static final String LISTING_COMMAND_DELETE_PREVIOUS_HEARINGS_AND_CREATE_NEXT_HEARING = "listing.command.delete-previous-hearings-and-create-next-hearing"; private static final String LISTING_COMMAND_UPDATE_HEARING_DAY_COURT_SCHEDULE = "listing.command.update-hearing-day-court-schedule"; + private static final String LISTING_COMMAND_MIGRATE_CROWN_HEARINGS_TO_COURTSCHEDULES = "listing.command.migrate-crown-hearings-to-courtschedules"; public static final String LISTING_COMMAND_UPDATE_HEARING_ADD_CASE_BDF = "listing.command.update-hearing-add-case-bdf"; private static final Logger LOGGER = LoggerFactory.getLogger(ListingCommandApi.class); private static final String PROSECUTION_CASES = "prosecutionCases"; @@ -422,6 +423,12 @@ public void handleUpdateHearingDayCourtSchedule(JsonEnvelope envelope) { envelope.payload())); } + @Handles("listing.migrate-crown-hearings-to-courtschedules") + public void handleMigrateCrownHearingsToCourtSchedules(JsonEnvelope envelope) { + sender.send(envelopeFrom(metadataFrom(envelope.metadata()).withName(LISTING_COMMAND_MIGRATE_CROWN_HEARINGS_TO_COURTSCHEDULES), + envelope.payload())); + } + @Handles("listing.mark-unallocated-hearing-as-duplicate") public void handleMarkUnallocatedHearingAsDuplicate(final JsonEnvelope envelope) { sender.send(envelopeFrom(metadataFrom(envelope.metadata()).withName(LISTING_COMMAND_DUPLICATE_UNALLOCATED_HEARING), diff --git a/listing-command/listing-command-api/src/main/resources/uk/gov/moj/cpp/listing/command/api/accesscontrol/listing-command-api.drl b/listing-command/listing-command-api/src/main/resources/uk/gov/moj/cpp/listing/command/api/accesscontrol/listing-command-api.drl index f87b40a53..918a89668 100644 --- a/listing-command/listing-command-api/src/main/resources/uk/gov/moj/cpp/listing/command/api/accesscontrol/listing-command-api.drl +++ b/listing-command/listing-command-api/src/main/resources/uk/gov/moj/cpp/listing/command/api/accesscontrol/listing-command-api.drl @@ -202,6 +202,15 @@ rule "Command - API - Action - listing.update-hearing-day-court-schedule" $outcome.setSuccess(true); end +rule "Command - API - Action - listing.migrate-crown-hearings-to-courtschedules" + when + $outcome: Outcome(); + $action: Action(name == "listing.migrate-crown-hearings-to-courtschedules"); + eval(userAndGroupProvider.isSystemUser($action)) + then + $outcome.setSuccess(true); +end + rule "Command - API - Action - listing.mark-unallocated-hearing-as-duplicate" when $outcome: Outcome(); diff --git a/listing-command/listing-command-api/src/raml/json/listing.migrate-crown-hearings-to-courtschedules.json b/listing-command/listing-command-api/src/raml/json/listing.migrate-crown-hearings-to-courtschedules.json new file mode 100644 index 000000000..a46c20600 --- /dev/null +++ b/listing-command/listing-command-api/src/raml/json/listing.migrate-crown-hearings-to-courtschedules.json @@ -0,0 +1,30 @@ +{ + "hearings": [ + { + "hearingId": "4182b7c6-6393-43f8-97ea-9a7e58b72ba4", + "hearingDayCourtSchedules": [ + { + "hearingDate": "2026-06-22", + "courtScheduleId": "df44e775-d809-4836-a14d-5a9f7c2078c1" + } + ] + }, + { + "hearingId": "7c2b9e10-4d33-4a8e-9b21-0a1f5c6d7e88", + "hearingDayCourtSchedules": [ + { + "hearingDate": "2026-07-14", + "courtScheduleId": "1b9a4f02-77c1-4e0a-8d3b-2c5e9f10a644" + }, + { + "hearingDate": "2026-07-15", + "courtScheduleId": "2c0b5103-88d2-4f1b-9e4c-3d6fa021b755" + }, + { + "hearingDate": "2026-07-16", + "courtScheduleId": "3d1c6214-99e3-401c-af5d-4e70b132c866" + } + ] + } + ] +} diff --git a/listing-command/listing-command-api/src/raml/json/schema/listing.migrate-crown-hearings-to-courtschedules.json b/listing-command/listing-command-api/src/raml/json/schema/listing.migrate-crown-hearings-to-courtschedules.json new file mode 100644 index 000000000..2751f7b74 --- /dev/null +++ b/listing-command/listing-command-api/src/raml/json/schema/listing.migrate-crown-hearings-to-courtschedules.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://justice.gov.uk/listing/courts/listing.migrate-crown-hearings-to-courtschedules.json", + "type": "object", + "properties": { + "hearings": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://justice.gov.uk/listing/courts/migrate-crown-hearing.json" + } + } + }, + "required": [ + "hearings" + ], + "additionalProperties": false +} diff --git a/listing-command/listing-command-api/src/raml/listing-command-api.raml b/listing-command/listing-command-api/src/raml/listing-command-api.raml index f0d378ebe..68aefb4d7 100644 --- a/listing-command/listing-command-api/src/raml/listing-command-api.raml +++ b/listing-command/listing-command-api/src/raml/listing-command-api.raml @@ -315,6 +315,25 @@ protocols: [ HTTP, HTTPS ] 202: description: Request accepted +/migrate-crown-hearings-to-courtschedules: + post: + description: | + Bulk-assign courtScheduleId to CROWN hearing days during court-schedule migration. + For each hearing, sets only the courtScheduleId on the matching hearing day; all other attributes are unchanged. + ... + (mapping): + requestType: application/vnd.listing.migrate-crown-hearings-to-courtschedules+json + name: listing.migrate-crown-hearings-to-courtschedules + ... + body: + application/vnd.listing.migrate-crown-hearings-to-courtschedules+json: + schema: !include json/schema/listing.migrate-crown-hearings-to-courtschedules.json + example: !include json/listing.migrate-crown-hearings-to-courtschedules.json + + responses: + 202: + description: Request accepted + /delete-hearing/{hearingId}: post: description: | diff --git a/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingAccessControlTest.java b/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingAccessControlTest.java index 9f8b3351e..444b866f8 100644 --- a/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingAccessControlTest.java +++ b/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingAccessControlTest.java @@ -44,6 +44,7 @@ public class ListingAccessControlTest extends BaseDroolsAccessControlTest { private static final String ACTION_CREATE_LISTING_NOTE = "listing.command.create-listing-note"; private static final String ACTION_DELETE_LISTING_NOTE = "listing.command.delete-listing-note"; private static final String ACTION_MARK_UNALLOCATED_HEARING_AS_DUPLICATE = "listing.mark-unallocated-hearing-as-duplicate"; + private static final String ACTION_MIGRATE_CROWN_HEARINGS_TO_COURTSCHEDULES = "listing.migrate-crown-hearings-to-courtschedules"; private static final String ACTION_DELETE_HEARING = "listing.command.delete-hearing"; private static final String ACTION_DELETE_PREVIOUS_HEARINGS_AND_CREATE_NEXT_HEARING = "listing.delete-previous-hearings-and-create-next-hearing"; @@ -266,6 +267,26 @@ public void shouldNotAllowNonSystemUserToMarkUnallocatedHearingAsDuplicate() { assertFailureOutcome(results); } + @Test + public void shouldAllowSystemUserToMigrateCrownHearingsToCourtSchedules() { + final Action action = createActionFor(ACTION_MIGRATE_CROWN_HEARINGS_TO_COURTSCHEDULES); + given(userAndGroupProvider.isSystemUser(action)).willReturn(true); + + final ExecutionResults results = executeRulesWith(action); + + assertSuccessfulOutcome(results); + } + + @Test + public void shouldNotAllowNonSystemUserToMigrateCrownHearingsToCourtSchedules() { + final Action action = createActionFor(ACTION_MIGRATE_CROWN_HEARINGS_TO_COURTSCHEDULES); + given(userAndGroupProvider.isSystemUser(action)).willReturn(false); + + final ExecutionResults results = executeRulesWith(action); + + assertFailureOutcome(results); + } + @Test public void shouldOnlyAllowHMCTSusersToEditNote() { diff --git a/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApiTest.java b/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApiTest.java index cd85ad3df..7e4fdb2d9 100644 --- a/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApiTest.java +++ b/listing-command/listing-command-api/src/test/java/uk/gov/moj/cpp/listing/command/api/ListingCommandApiTest.java @@ -883,6 +883,22 @@ public void shouldDeleteListingNote() { assertThat(senderJsonEnvelopeCaptor.getValue().metadata().name(), is("listing.command.handler.delete-listing-note")); } + @Test + public void shouldForwardMigrateCrownHearingsToCourtSchedulesToCommandHandler() { + final JsonEnvelope command = mock(JsonEnvelope.class); + final MetadataBuilder metadataBuilder = metadataWithRandomUUID("listing.migrate-crown-hearings-to-courtschedules"); + when(command.metadata()).thenReturn(metadataBuilder.build()); + final JsonObject payload = mock(JsonObject.class); + when(command.payload()).thenReturn(payload); + + final ArgumentCaptor senderEnvelopeCaptor = forClass(Envelope.class); + listingCommandApi.handleMigrateCrownHearingsToCourtSchedules(command); + + verify(sender).send(senderEnvelopeCaptor.capture()); + assertThat(senderEnvelopeCaptor.getValue().metadata().name(), is("listing.command.migrate-crown-hearings-to-courtschedules")); + assertThat(senderEnvelopeCaptor.getValue().payload(), is(payload)); + } + @Test public void shouldEditNote() { final JsonEnvelope command = mock(JsonEnvelope.class); diff --git a/listing-command/listing-command-handler/src/main/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandler.java b/listing-command/listing-command-handler/src/main/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandler.java index a9ecb14dd..ca7b8012e 100644 --- a/listing-command/listing-command-handler/src/main/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandler.java +++ b/listing-command/listing-command-handler/src/main/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandler.java @@ -187,6 +187,7 @@ public class ListingCommandHandler { public static final String HEARING_ID = "hearingId"; private static final String HEARING_DAY_COURT_SCHEDULES = "hearingDayCourtSchedules"; + private static final String HEARINGS = "hearings"; private static final String PROSECUTION_CASE = "prosecutionCase"; public static final String OUCODE = "oucode"; @@ -1423,15 +1424,32 @@ public void correctHearingDaysWithoutCourtCentre(final JsonEnvelope commandEnvel public void updateHearingDayCourtSchedule(final JsonEnvelope commandEnvelope) throws EventStreamException { final JsonObject payload = commandEnvelope.payloadAsJsonObject(); final UUID hearingId = fromString(payload.getString(HEARING_ID)); + final List hearingDayCourtSchedules = toHearingDayCourtSchedules(payload); + updateHearingEventStream(commandEnvelope, + hearingId, + hearing -> hearing.raiseHearingDayCourtSchedulesUpdated(hearingId, hearingDayCourtSchedules)); + } + + @Handles("listing.command.migrate-crown-hearings-to-courtschedules") + public void migrateCrownHearingsToCourtSchedules(final JsonEnvelope commandEnvelope) throws EventStreamException { + final JsonObject payload = commandEnvelope.payloadAsJsonObject(); + for (final JsonObject hearing : payload.getJsonArray(HEARINGS).getValuesAs(JsonObject.class)) { + final UUID hearingId = fromString(hearing.getString(HEARING_ID)); + final List hearingDayCourtSchedules = toHearingDayCourtSchedules(hearing); + final EventStream eventStream = eventSource.getStreamById(hearingId); + final Hearing hearingAggregate = aggregateService.get(eventStream, Hearing.class); + appendEventsToStream(commandEnvelope, eventStream, + hearingAggregate.raiseCrownHearingMigratedToCourtSchedule(hearingId, hearingDayCourtSchedules)); + } + } + + private List toHearingDayCourtSchedules(final JsonObject source) { final List hearingDayCourtSchedules = new ArrayList<>(); - payload.getJsonArray(HEARING_DAY_COURT_SCHEDULES) + source.getJsonArray(HEARING_DAY_COURT_SCHEDULES) .getValuesAs(JsonObject.class) - .stream() .forEach(hearingDayCourtSchedule -> hearingDayCourtSchedules.add( jsonObjectConverter.convert(hearingDayCourtSchedule, HearingDayCourtSchedule.class))); - updateHearingEventStream(commandEnvelope, - hearingId, - hearing -> hearing.raiseHearingDayCourtSchedulesUpdated(hearingId, hearingDayCourtSchedules)); + return hearingDayCourtSchedules; } @Handles("listing.command.update-cps-prosecutor-with-associated-hearings") diff --git a/listing-command/listing-command-handler/src/raml/json/listing.command.migrate-crown-hearings-to-courtschedules.json b/listing-command/listing-command-handler/src/raml/json/listing.command.migrate-crown-hearings-to-courtschedules.json new file mode 100644 index 000000000..a46c20600 --- /dev/null +++ b/listing-command/listing-command-handler/src/raml/json/listing.command.migrate-crown-hearings-to-courtschedules.json @@ -0,0 +1,30 @@ +{ + "hearings": [ + { + "hearingId": "4182b7c6-6393-43f8-97ea-9a7e58b72ba4", + "hearingDayCourtSchedules": [ + { + "hearingDate": "2026-06-22", + "courtScheduleId": "df44e775-d809-4836-a14d-5a9f7c2078c1" + } + ] + }, + { + "hearingId": "7c2b9e10-4d33-4a8e-9b21-0a1f5c6d7e88", + "hearingDayCourtSchedules": [ + { + "hearingDate": "2026-07-14", + "courtScheduleId": "1b9a4f02-77c1-4e0a-8d3b-2c5e9f10a644" + }, + { + "hearingDate": "2026-07-15", + "courtScheduleId": "2c0b5103-88d2-4f1b-9e4c-3d6fa021b755" + }, + { + "hearingDate": "2026-07-16", + "courtScheduleId": "3d1c6214-99e3-401c-af5d-4e70b132c866" + } + ] + } + ] +} diff --git a/listing-command/listing-command-handler/src/raml/json/schema/listing.command.migrate-crown-hearings-to-courtschedules.json b/listing-command/listing-command-handler/src/raml/json/schema/listing.command.migrate-crown-hearings-to-courtschedules.json new file mode 100644 index 000000000..03d2325f7 --- /dev/null +++ b/listing-command/listing-command-handler/src/raml/json/schema/listing.command.migrate-crown-hearings-to-courtschedules.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://justice.gov.uk/listing/courts/listing.command.migrate-crown-hearings-to-courtschedules.json", + "type": "object", + "properties": { + "hearings": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://justice.gov.uk/listing/courts/migrate-crown-hearing.json" + } + } + }, + "required": [ + "hearings" + ], + "additionalProperties": false +} diff --git a/listing-command/listing-command-handler/src/raml/listing-command-handler.messaging.raml b/listing-command/listing-command-handler/src/raml/listing-command-handler.messaging.raml index cb7060834..b400b48b1 100644 --- a/listing-command/listing-command-handler/src/raml/listing-command-handler.messaging.raml +++ b/listing-command/listing-command-handler/src/raml/listing-command-handler.messaging.raml @@ -225,6 +225,10 @@ baseUri: message://command/handler/message/listing schema: !include json/schema/listing.command.update-hearing-day-court-schedule.json example: !include json/listing.command.update-hearing-day-court-schedule.json + application/vnd.listing.command.migrate-crown-hearings-to-courtschedules+json: + schema: !include json/schema/listing.command.migrate-crown-hearings-to-courtschedules.json + example: !include json/listing.command.migrate-crown-hearings-to-courtschedules.json + application/vnd.listing.command.mark-hearing-as-duplicate+json: example: !include json/listing.command.mark-hearing-as-duplicate.json schema: !include json/schema/listing.command.mark-hearing-as-duplicate.json diff --git a/listing-command/listing-command-handler/src/test/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandlerTest.java b/listing-command/listing-command-handler/src/test/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandlerTest.java index 3b377d10a..b86d501f0 100644 --- a/listing-command/listing-command-handler/src/test/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandlerTest.java +++ b/listing-command/listing-command-handler/src/test/java/uk/gov/moj/cpp/listing/command/handler/ListingCommandHandlerTest.java @@ -4025,6 +4025,34 @@ public void shouldHandleUpdateHearingDaysWithoutCourtSchedule() throws EventStre verify(hearing).raiseHearingDayCourtSchedulesUpdated(any(), any()); } + @Test + public void shouldMigrateCrownHearingsToCourtSchedulesForEachHearing() throws EventStreamException, IOException { + final UUID hearingId1 = randomUUID(); + final UUID hearingId2 = randomUUID(); + final String daysJson1 = "[{\"hearingDate\":\"2026-06-22\",\"courtScheduleId\":\"df44e775-d809-4836-a14d-5a9f7c2078c1\"}]"; + final String daysJson2 = "[{\"hearingDate\":\"2026-07-14\",\"courtScheduleId\":\"1b9a4f02-77c1-4e0a-8d3b-2c5e9f10a644\"},{\"hearingDate\":\"2026-07-15\",\"courtScheduleId\":\"2c0b5103-88d2-4f1b-9e4c-3d6fa021b755\"}]"; + + final JsonArray hearings = createArrayBuilder() + .add(createObjectBuilder() + .add("hearingId", hearingId1.toString()) + .add("hearingDayCourtSchedules", objectMapper.readValue(daysJson1, JsonArray.class))) + .add(createObjectBuilder() + .add("hearingId", hearingId2.toString()) + .add("hearingDayCourtSchedules", objectMapper.readValue(daysJson2, JsonArray.class))) + .build(); + final JsonObject payload = createObjectBuilder().add("hearings", hearings).build(); + + final JsonEnvelope commandEnvelope = envelopeFrom(metadataWithRandomUUID("listing.command.migrate-crown-hearings-to-courtschedules"), payload); + when(eventSource.getStreamById(any(UUID.class))).thenReturn(eventStream); + + listingCommandHandler.migrateCrownHearingsToCourtSchedules(commandEnvelope); + + verify(eventSource).getStreamById(hearingId1); + verify(eventSource).getStreamById(hearingId2); + verify(hearing, times(2)).raiseCrownHearingMigratedToCourtSchedule(any(), any()); + verify(eventStream, times(2)).append(any()); + } + private void shouldRequestPublicationOfACourtListForAllCrownCourtsAsExpected(final LocalDate dayOfRequest, final LocalDate expectedListingDate) { fixClock(dayOfRequest.atStartOfDay().toInstant(ZoneOffset.UTC)); diff --git a/listing-domain/listing-domain-aggregate/src/main/java/uk/gov/moj/cpp/listing/domain/aggregate/Hearing.java b/listing-domain/listing-domain-aggregate/src/main/java/uk/gov/moj/cpp/listing/domain/aggregate/Hearing.java index 2c7984aed..dcebdf9ee 100644 --- a/listing-domain/listing-domain-aggregate/src/main/java/uk/gov/moj/cpp/listing/domain/aggregate/Hearing.java +++ b/listing-domain/listing-domain-aggregate/src/main/java/uk/gov/moj/cpp/listing/domain/aggregate/Hearing.java @@ -89,6 +89,7 @@ import uk.gov.justice.listing.events.HearingAllocatedForListing; import uk.gov.justice.listing.events.HearingAllocatedForListingV2; import uk.gov.justice.listing.events.HearingChangesSaved; +import uk.gov.justice.listing.events.CrownHearingMigratedToCourtschedule; import uk.gov.justice.listing.events.HearingDayCourtSchedule; import uk.gov.justice.listing.events.HearingDayCourtScheduleUpdated; import uk.gov.justice.listing.events.HearingDaysCancelled; @@ -260,6 +261,7 @@ public Object apply(final Object event) { when(NonDefaultDaysAssignedToHearing.class).apply(this::onNonDefaultDaysAssignedToHearing), when(HearingDaysChangedForHearing.class).apply(this::onHearingDaysChangedForHearing), when(HearingDayCourtScheduleUpdated.class).apply(this::onHearingDayCourtScheduleUpdated), + when(CrownHearingMigratedToCourtschedule.class).apply(this::onCrownHearingMigratedToCourtSchedule), when(HearingDaysCancelled.class).apply(this::onHearingDaysCancelledForHearing), when(JurisdictionChangedForHearing.class).apply(this::onJurisdictionChangedForHearing), when(JudiciaryAssignedToHearing.class).apply(this::onJudiciaryAssignedToHearing), @@ -3177,8 +3179,14 @@ private void onHearingDaysWithoutCourtCentreCorrected(final HearingDaysWithoutCo } private void onHearingDayCourtScheduleUpdated(final HearingDayCourtScheduleUpdated hearingDayCourtScheduleUpdated) { - final List hearingDayCourtSchedules = hearingDayCourtScheduleUpdated.getHearingDayCourtSchedules(); + mergeCourtScheduleIdsByHearingDate(hearingDayCourtScheduleUpdated.getHearingDayCourtSchedules()); + } + + private void onCrownHearingMigratedToCourtSchedule(final CrownHearingMigratedToCourtschedule crownHearingMigratedToCourtschedule) { + mergeCourtScheduleIdsByHearingDate(crownHearingMigratedToCourtschedule.getHearingDayCourtSchedules()); + } + private void mergeCourtScheduleIdsByHearingDate(final List hearingDayCourtSchedules) { if (isEmpty(hearingDayCourtSchedules) || isEmpty(hearingDays)) { return; } @@ -3413,6 +3421,14 @@ public Stream raiseHearingDayCourtSchedulesUpdated(UUID hearingId, .build())); } + public Stream raiseCrownHearingMigratedToCourtSchedule(UUID hearingId, + List hearingDayCourtSchedules) { + return apply(Stream.of(CrownHearingMigratedToCourtschedule.crownHearingMigratedToCourtschedule() + .withHearingId(hearingId) + .withHearingDayCourtSchedules(hearingDayCourtSchedules) + .build())); + } + public Stream changeNextHearingDate(final UUID hearingId) { Stream events = Stream.empty(); diff --git a/listing-domain/listing-domain-aggregate/src/test/java/uk/gov/moj/cpp/listing/domain/aggregate/HearingAggregateTest.java b/listing-domain/listing-domain-aggregate/src/test/java/uk/gov/moj/cpp/listing/domain/aggregate/HearingAggregateTest.java index 7713c64fd..3df735766 100644 --- a/listing-domain/listing-domain-aggregate/src/test/java/uk/gov/moj/cpp/listing/domain/aggregate/HearingAggregateTest.java +++ b/listing-domain/listing-domain-aggregate/src/test/java/uk/gov/moj/cpp/listing/domain/aggregate/HearingAggregateTest.java @@ -39,6 +39,8 @@ import uk.gov.justice.listing.events.CasesAddedToHearing; import uk.gov.justice.listing.events.CourtApplicationAddedForHearing; import uk.gov.justice.listing.events.CourtListRestricted; +import uk.gov.justice.listing.events.CrownHearingMigratedToCourtschedule; +import uk.gov.justice.listing.events.HearingDayCourtSchedule; import uk.gov.justice.listing.events.CourtRoomRemovedFromHearing; import uk.gov.justice.listing.events.Defendant; import uk.gov.justice.listing.events.DefendantCourtProceedingsUpdatedV2; @@ -61,6 +63,7 @@ import uk.gov.justice.listing.events.Marker; import uk.gov.justice.listing.events.NewDefendantAddedForCourtProceedings; import uk.gov.justice.listing.events.NewDefendantDetailsUpdated; +import uk.gov.justice.listing.events.NonDefaultDaysAssignedToHearing; import uk.gov.justice.listing.events.Offence; import uk.gov.justice.listing.events.OffenceAdded; import uk.gov.justice.listing.events.OffenceDeleted; @@ -151,6 +154,105 @@ class HearingAggregateTest { private static final Logger LOGGER = Logger.getLogger(HearingAggregateTest.class.getName()); + @Test + public void shouldRaiseCrownHearingMigratedToCourtScheduleEvent() { + final UUID courtScheduleId = randomUUID(); + final LocalDate hearingDate = now(); + final List schedules = + singletonList(new HearingDayCourtSchedule(courtScheduleId, hearingDate)); + + final List events = hearing.raiseCrownHearingMigratedToCourtSchedule(hearingId, schedules) + .collect(Collectors.toList()); + + assertThat(events, hasSize(1)); + assertThat(events.get(0), CoreMatchers.instanceOf(CrownHearingMigratedToCourtschedule.class)); + final CrownHearingMigratedToCourtschedule event = (CrownHearingMigratedToCourtschedule) events.get(0); + assertThat(event.getHearingId(), is(hearingId)); + assertThat(event.getHearingDayCourtSchedules(), hasSize(1)); + assertThat(event.getHearingDayCourtSchedules().get(0).getCourtScheduleId(), is(courtScheduleId)); + assertThat(event.getHearingDayCourtSchedules().get(0).getHearingDate(), is(hearingDate)); + } + + @Test + public void shouldOnlySetCourtScheduleIdOnMatchingDayLeavingAllOtherHearingDayAndNonDefaultDayFieldsUnchanged() { + final LocalDate migratedDate = LocalDate.of(2027, 8, 17); + final LocalDate untouchedDate = LocalDate.of(2027, 8, 18); + final ZonedDateTime migratedStart = ZonedDateTime.of(migratedDate, LocalTime.of(10, 0), UTC); + final ZonedDateTime untouchedStart = ZonedDateTime.of(untouchedDate, LocalTime.of(10, 0), UTC); + + final UUID oldScheduleId1 = randomUUID(); + final UUID oldScheduleId2 = randomUUID(); + final UUID newScheduleId = randomUUID(); + final UUID roomId1 = randomUUID(); + final UUID roomId2 = randomUUID(); + final UUID centreId1 = randomUUID(); + final UUID centreId2 = randomUUID(); + + // Seed hearingDays (+ currentHearingEventState) with two CROWN days, each already carrying a courtScheduleId. + hearing.apply(HearingListed.hearingListed() + .withHearing(uk.gov.justice.listing.events.Hearing.hearing() + .withId(hearingId) + .withType(uk.gov.justice.listing.events.Type.type().withDescription("First Hearing").build()) + .withHearingLanguage(HearingLanguage.ENGLISH) + .withJurisdictionType(uk.gov.justice.core.courts.JurisdictionType.CROWN) + .withHearingDays(List.of( + HearingDay.hearingDay().withHearingDate(migratedDate).withStartTime(migratedStart) + .withDurationMinutes(60).withCourtScheduleId(oldScheduleId1) + .withCourtRoomId(roomId1).withCourtCentreId(centreId1).build(), + HearingDay.hearingDay().withHearingDate(untouchedDate).withStartTime(untouchedStart) + .withDurationMinutes(90).withCourtScheduleId(oldScheduleId2) + .withCourtRoomId(roomId2).withCourtCentreId(centreId2).build())) + .build()) + .build()); + + // Seed full-fidelity nonDefaultDays (session/oucode/duration/courtScheduleId) matching the same two days. + hearing.apply(NonDefaultDaysAssignedToHearing.nonDefaultDaysAssignedToHearing() + .withHearingId(hearingId) + .withIsPublicEvent(false) + .withNonDefaultDays(List.of( + uk.gov.justice.listing.events.NonDefaultDay.nonDefaultDay() + .withStartTime(migratedStart).withDuration(60).withSession("AM").withOucode("OU1") + .withCourtScheduleId(oldScheduleId1.toString()).build(), + uk.gov.justice.listing.events.NonDefaultDay.nonDefaultDay() + .withStartTime(untouchedStart).withDuration(90).withSession("PM").withOucode("OU2") + .withCourtScheduleId(oldScheduleId2.toString()).build())) + .build()); + + // Migrate: supply a new courtScheduleId only for the first day. + hearing.raiseCrownHearingMigratedToCourtSchedule(hearingId, + singletonList(new HearingDayCourtSchedule(newScheduleId, migratedDate))) + .collect(Collectors.toList()); + + final uk.gov.justice.listing.events.Hearing state = hearing.getCurrentHearingEventState(); + + // --- hearingDays: only the matching day's courtScheduleId changes; everything else is preserved --- + final HearingDay migratedDay = state.getHearingDays().stream() + .filter(hd -> migratedDate.equals(hd.getHearingDate())).findFirst().get(); + final HearingDay untouchedDay = state.getHearingDays().stream() + .filter(hd -> untouchedDate.equals(hd.getHearingDate())).findFirst().get(); + + assertThat(migratedDay.getCourtScheduleId(), is(newScheduleId)); + assertThat(migratedDay.getCourtRoomId(), is(roomId1)); + assertThat(migratedDay.getCourtCentreId(), is(centreId1)); + assertThat(migratedDay.getDurationMinutes(), is(60)); + assertThat(untouchedDay.getCourtScheduleId(), is(oldScheduleId2)); + assertThat(untouchedDay.getCourtRoomId(), is(roomId2)); + assertThat(untouchedDay.getCourtCentreId(), is(centreId2)); + + // --- nonDefaultDays: same guarantee, including the non-courtScheduleId fields (session/oucode/duration) --- + final uk.gov.justice.listing.events.NonDefaultDay migratedNonDefaultDay = state.getNonDefaultDays().stream() + .filter(nd -> migratedDate.equals(nd.getStartTime().toLocalDate())).findFirst().get(); + final uk.gov.justice.listing.events.NonDefaultDay untouchedNonDefaultDay = state.getNonDefaultDays().stream() + .filter(nd -> untouchedDate.equals(nd.getStartTime().toLocalDate())).findFirst().get(); + + assertThat(migratedNonDefaultDay.getCourtScheduleId(), is(newScheduleId.toString())); + assertThat(migratedNonDefaultDay.getSession(), is("AM")); + assertThat(migratedNonDefaultDay.getOucode(), is("OU1")); + assertThat(migratedNonDefaultDay.getDuration(), is(60)); + assertThat(untouchedNonDefaultDay.getCourtScheduleId(), is(oldScheduleId2.toString())); + assertThat(untouchedNonDefaultDay.getSession(), is("PM")); + assertThat(untouchedNonDefaultDay.getOucode(), is("OU2")); + } diff --git a/listing-event/listing-event-listener/src/main/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListener.java b/listing-event/listing-event-listener/src/main/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListener.java index ae779dba0..663d4eb90 100644 --- a/listing-event/listing-event-listener/src/main/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListener.java +++ b/listing-event/listing-event-listener/src/main/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListener.java @@ -5,7 +5,9 @@ import static uk.gov.moj.cpp.listing.persistence.repository.JsonEntityFinder.using; import static utils.HearingDayUtil.getNotCancelledHearingDays; +import uk.gov.justice.listing.events.CrownHearingMigratedToCourtschedule; import uk.gov.justice.listing.events.HearingDay; +import uk.gov.justice.listing.events.HearingDayCourtSchedule; import uk.gov.justice.listing.events.HearingDayCourtScheduleUpdated; import uk.gov.justice.listing.events.HearingDaysWithoutCourtCentreCorrected; import uk.gov.justice.listing.events.NonDefaultDay; @@ -86,10 +88,19 @@ public void hearingDaysWithoutCourtCentreCorrected(final Envelope event) throws JsonProcessingException { final HearingDayCourtScheduleUpdated hearingDaysEvent = event.payload(); - final UUID hearingId = hearingDaysEvent.getHearingId(); + mergeCourtScheduleIds(hearingDaysEvent.getHearingId(), hearingDaysEvent.getHearingDayCourtSchedules()); + } + + @Handles("listing.events.crown-hearing-migrated-to-courtschedule") + public void crownHearingMigratedToCourtSchedule(Envelope event) throws JsonProcessingException { + final CrownHearingMigratedToCourtschedule migratedEvent = event.payload(); + mergeCourtScheduleIds(migratedEvent.getHearingId(), migratedEvent.getHearingDayCourtSchedules()); + } + + private void mergeCourtScheduleIds(final UUID hearingId, final List hearingDayCourtSchedules) throws JsonProcessingException { final uk.gov.justice.listing.events.Hearing existingHearing = findHearingBy(hearingId); final Map scheduledHearingDateMap = new HashMap<>(); - hearingDaysEvent.getHearingDayCourtSchedules().forEach( + hearingDayCourtSchedules.forEach( hd -> scheduledHearingDateMap.put(hd.getHearingDate(), hd.getCourtScheduleId())); updateHearingDays(existingHearing, scheduledHearingDateMap); diff --git a/listing-event/listing-event-listener/src/test/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListenerTest.java b/listing-event/listing-event-listener/src/test/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListenerTest.java index 06aeaaa1b..7f3798950 100644 --- a/listing-event/listing-event-listener/src/test/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListenerTest.java +++ b/listing-event/listing-event-listener/src/test/java/uk/gov/moj/cpp/listing/event/listener/HearingDaysUpdateEventListenerTest.java @@ -20,6 +20,7 @@ import static uk.gov.justice.services.test.utils.core.messaging.MetadataBuilderFactory.metadataWithRandomUUID; import uk.gov.justice.listing.events.HearingDay; +import uk.gov.justice.listing.events.CrownHearingMigratedToCourtschedule; import uk.gov.justice.listing.events.HearingDayCourtSchedule; import uk.gov.justice.listing.events.HearingDayCourtScheduleUpdated; import uk.gov.justice.listing.events.HearingDaysWithoutCourtCentreCorrected; @@ -160,6 +161,45 @@ public void testUpdateHearingDayCourtSchedule() throws JsonProcessingException { verify(hearingSearchSyncService, times(1)).sync(hearingId); } + @Test + public void testCrownHearingMigratedToCourtSchedule() throws JsonProcessingException { + UUID hearingId = randomUUID(); + UUID courtScheduleId = randomUUID(); + LocalDate hearingDate1 = LocalDate.now(); + LocalDate hearingDate2 = LocalDate.now().plusDays(1); + + List hearingDayCourtSchedules = + of(new HearingDayCourtSchedule(courtScheduleId, hearingDate1), + new HearingDayCourtSchedule(courtScheduleId, hearingDate2)); + CrownHearingMigratedToCourtschedule event = + CrownHearingMigratedToCourtschedule.crownHearingMigratedToCourtschedule() + .withHearingId(hearingId) + .withHearingDayCourtSchedules(hearingDayCourtSchedules) + .build(); + Envelope listenerEnvelope = envelopeFrom( + metadataWithRandomUUID("listing.events.crown-hearing-migrated-to-courtschedule"), + event); + + List hearingDays = + of(HearingDay.hearingDay().withHearingDate(hearingDate1).withCourtScheduleId(courtScheduleId).build(), + HearingDay.hearingDay().withHearingDate(hearingDate2).withCourtScheduleId(courtScheduleId).build()); + Set dbHearingDays = Set.of(HearingDays.builder().withHearingDate(hearingDate1).build(), + HearingDays.builder().withHearingDate(hearingDate2).build()); + uk.gov.justice.listing.events.Hearing dbHearingPayload = + uk.gov.justice.listing.events.Hearing.hearing().withId(hearingId).withHearingDays(hearingDays).build(); + + Hearing hearing = Hearing.builder() + .withId(hearingId) + .withProperties(objectMapper.valueToTree(dbHearingPayload)) + .withHearingDays(dbHearingDays) + .build(); + when(hearingRepository.findBy(hearingId)).thenReturn(hearing); + hearingDaysUpdateEventListener.crownHearingMigratedToCourtSchedule(listenerEnvelope); + + verify(hearingRepository, times(1)).save(hearing); + verify(hearingSearchSyncService, times(1)).sync(hearingId); + } + @Test public void testHearingDaysWithoutCourtCentreCorrectedWithNullCourtRoomId() throws JsonProcessingException { // Given: courtRoomId is null, which was causing the NullPointerException diff --git a/listing-event/listing-event-listener/src/yaml/json/listing.events.crown-hearing-migrated-to-courtschedule.json b/listing-event/listing-event-listener/src/yaml/json/listing.events.crown-hearing-migrated-to-courtschedule.json new file mode 100644 index 000000000..62ccdc4e7 --- /dev/null +++ b/listing-event/listing-event-listener/src/yaml/json/listing.events.crown-hearing-migrated-to-courtschedule.json @@ -0,0 +1,9 @@ +{ + "hearingId": "4182b7c6-6393-43f8-97ea-9a7e58b72ba4", + "hearingDayCourtSchedules": [ + { + "hearingDate": "2026-06-22", + "courtScheduleId": "df44e775-d809-4836-a14d-5a9f7c2078c1" + } + ] +} diff --git a/listing-event/listing-event-listener/src/yaml/json/schema/listing.events.crown-hearing-migrated-to-courtschedule.json b/listing-event/listing-event-listener/src/yaml/json/schema/listing.events.crown-hearing-migrated-to-courtschedule.json new file mode 100644 index 000000000..78c07b103 --- /dev/null +++ b/listing-event/listing-event-listener/src/yaml/json/schema/listing.events.crown-hearing-migrated-to-courtschedule.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://justice.gov.uk/listing/events/listing.events.crown-hearing-migrated-to-courtschedule.json", + "type": "object", + "properties": { + "hearingId": { + "$ref": "http://justice.gov.uk/listing/events/listing-definitions.json#/definitions/uuid" + }, + "hearingDayCourtSchedules": { + "type": "array", + "items": { + "$ref": "http://justice.gov.uk/listing/events/hearing-day-court-schedule.json" + } + } + }, + "required": [ + "hearingId", + "hearingDayCourtSchedules" + ], + "additionalProperties": false +} diff --git a/listing-event/listing-event-listener/src/yaml/subscriptions-descriptor.yaml b/listing-event/listing-event-listener/src/yaml/subscriptions-descriptor.yaml index 92bd25df6..3afb2511e 100644 --- a/listing-event/listing-event-listener/src/yaml/subscriptions-descriptor.yaml +++ b/listing-event/listing-event-listener/src/yaml/subscriptions-descriptor.yaml @@ -221,6 +221,9 @@ subscriptions_descriptor: - name: listing.events.hearing-day-court-schedule-updated schema_uri: http://justice.gov.uk/listing/events/listing.events.hearing-day-court-schedule-updated.json + - name: listing.events.crown-hearing-migrated-to-courtschedule + schema_uri: http://justice.gov.uk/listing/events/listing.events.crown-hearing-migrated-to-courtschedule.json + - name: listing.events.hearing-marked-as-duplicate schema_uri: http://justice.gov.uk/listing/events/listing.events.hearing-marked-as-duplicate.json diff --git a/listing-integration-test/src/test/java/uk/gov/moj/cpp/listing/it/MigrateCrownHearingsToCourtSchedulesIT.java b/listing-integration-test/src/test/java/uk/gov/moj/cpp/listing/it/MigrateCrownHearingsToCourtSchedulesIT.java new file mode 100644 index 000000000..8ee05234e --- /dev/null +++ b/listing-integration-test/src/test/java/uk/gov/moj/cpp/listing/it/MigrateCrownHearingsToCourtSchedulesIT.java @@ -0,0 +1,97 @@ +package uk.gov.moj.cpp.listing.it; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.withJsonPath; +import static java.text.MessageFormat.format; +import static java.util.Collections.singletonList; +import static java.util.UUID.randomUUID; +import static org.apache.http.HttpStatus.SC_ACCEPTED; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static uk.gov.justice.core.courts.JurisdictionType.CROWN; +import static uk.gov.moj.cpp.listing.utils.CourtSchedulerServiceStub.stubGetCourtSchedulesByIdWithDraftStatus; +import static uk.gov.moj.cpp.listing.utils.CourtSchedulerServiceStub.stubListHearingInCourtSessions; +import static uk.gov.moj.cpp.listing.utils.PropertyUtil.getBaseUri; +import static uk.gov.moj.cpp.listing.utils.PropertyUtil.readConfig; +import static uk.gov.moj.cpp.listing.utils.ReferenceDataStub.getRandomCourtCenterId; +import static uk.gov.moj.cpp.listing.utils.ReferenceDataStub.getRandomCourtRoomId; + +import uk.gov.moj.cpp.listing.helper.SearchHearingHelper; +import uk.gov.moj.cpp.listing.steps.ListCourtHearingSteps; +import uk.gov.moj.cpp.listing.steps.data.CaseAndDefendantData; +import uk.gov.moj.cpp.listing.steps.data.HearingsData; + +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.UUID; + +import javax.json.Json; +import javax.ws.rs.core.Response; + +import org.hamcrest.Matcher; +import org.junit.jupiter.api.Test; + +public class MigrateCrownHearingsToCourtSchedulesIT extends AbstractIT { + + private static final String MIGRATE_COMMAND_URL = "listing.migrate-crown-hearings-to-courtschedules"; + private static final String MIGRATE_MEDIA_TYPE = "application/vnd.listing.migrate-crown-hearings-to-courtschedules+json"; + + private static final String SEEDED_COURT_SCHEDULE_ID = "8e837de0-743a-4a2c-9db3-b2e678c48729"; + + private final UUID hearingId = randomUUID(); + private final UUID courtCentreId = getRandomCourtCenterId(); + private final UUID courtRoomId = getRandomCourtRoomId(); + private final LocalDate hearingDate = LocalDate.of(2027, 8, 17); + private final ZonedDateTime hearingStartTime = ZonedDateTime.of(2027, 8, 17, 10, 30, 0, 0, ZoneOffset.UTC); + + @Test + public void shouldMigrateOnlyCourtScheduleIdOnTheMatchingHearingDay() { + givenAUserHasLoggedInAsAListingOfficer(USER_ID_VALUE); + + final CaseAndDefendantData caseAndDefendantData = new CaseAndDefendantData( + hearingId, null, "CASE_URN_MIGRATE", randomUUID(), null, CROWN.name(), CROWN.name(), null, null); + + final HearingsData hearingsData = HearingsData.hearingsDataWithAllocationDataAndJudiciary( + caseAndDefendantData, courtCentreId, courtRoomId, hearingDate, hearingStartTime); + + final ListCourtHearingSteps listCourtHearingSteps = new ListCourtHearingSteps(hearingsData); + stubGetCourtSchedulesByIdWithDraftStatus(singletonList(SEEDED_COURT_SCHEDULE_ID), false); + stubListHearingInCourtSessions(hearingId.toString(), SEEDED_COURT_SCHEDULE_ID, hearingStartTime); + listCourtHearingSteps.whenCaseIsSubmittedForListing(); + listCourtHearingSteps.verifyHearingIsCreated(hearingId, 1); + listCourtHearingSteps.verifyHearingListedFromAPI(true); + + final UUID newCourtScheduleId = randomUUID(); + final Response response = restClient.postCommand(migrateCommandUrl(), MIGRATE_MEDIA_TYPE, + migratePayload(newCourtScheduleId), getLoggedInHeader()); + assertThat(response.getStatus(), equalTo(SC_ACCEPTED)); + + final String hearingFilter = SearchHearingHelper.getHearingFilter(hearingId.toString()); + SearchHearingHelper.pollForHearing(courtCentreId.toString(), ALLOCATED, getLoggedInUser().toString(), new Matcher[]{ + withJsonPath(hearingFilter + ".hearingDays[?(@.hearingDate == '" + hearingDate + "')].courtScheduleId", + hasItem(newCourtScheduleId.toString())), + withJsonPath(hearingFilter + ".hearingDays[?(@.hearingDate == '" + hearingDate + "')].courtRoomId", + hasItem(courtRoomId.toString())), + withJsonPath(hearingFilter + ".hearingDays[?(@.hearingDate == '" + hearingDate + "')].courtCentreId", + hasItem(courtCentreId.toString())) + }); + } + + private String migrateCommandUrl() { + return String.format("%s/%s", getBaseUri(), format(readConfig().getProperty(MIGRATE_COMMAND_URL))); + } + + private String migratePayload(final UUID newCourtScheduleId) { + return Json.createObjectBuilder() + .add("hearings", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("hearingId", hearingId.toString()) + .add("hearingDayCourtSchedules", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("hearingDate", hearingDate.toString()) + .add("courtScheduleId", newCourtScheduleId.toString()))))) + .build() + .toString(); + } +} diff --git a/listing-integration-test/src/test/resources/endpoint.properties b/listing-integration-test/src/test/resources/endpoint.properties index 8d9ef3764..fbe0fde74 100644 --- a/listing-integration-test/src/test/resources/endpoint.properties +++ b/listing-integration-test/src/test/resources/endpoint.properties @@ -28,6 +28,7 @@ listing.search.hearings.by.allocated.court-centre-id.authority-code.hearing-type listing.command.update-hearing-for-listing=listing-service/command/api/rest/listing/hearings/{0} listing.command.update-hearings-for-listing=listing-service/command/api/rest/listing/hearings/update-hearings listing.update-hearing-day-court-schedule=listing-service/command/api/rest/listing/update-hearing-day-court-schedule +listing.migrate-crown-hearings-to-courtschedules=listing-service/command/api/rest/listing/migrate-crown-hearings-to-courtschedules listing.command.change-judiciary-for-hearings=listing-service/command/api/rest/listing/hearings listing.command.sequence-hearings=listing-service/command/api/rest/listing/hearings/sequence public.progression.case-defendant-changed=listing-service/event/processor/message/listing diff --git a/listing-json/src/main/resources/json/schema/migrate-crown-hearing.json b/listing-json/src/main/resources/json/schema/migrate-crown-hearing.json new file mode 100644 index 000000000..9e645cc38 --- /dev/null +++ b/listing-json/src/main/resources/json/schema/migrate-crown-hearing.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://justice.gov.uk/listing/courts/migrate-crown-hearing.json", + "type": "object", + "properties": { + "hearingId": { + "$ref": "http://justice.gov.uk/core/courts/courtsDefinitions.json#/definitions/uuid" + }, + "hearingDayCourtSchedules": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://justice.gov.uk/listing/events/hearing-day-court-schedule.json" + } + } + }, + "required": [ + "hearingId", + "hearingDayCourtSchedules" + ], + "additionalProperties": false +}