diff --git a/hearing-command/hearing-command-handler/pom.xml b/hearing-command/hearing-command-handler/pom.xml
index 18a434b96b..c2eb77becf 100644
--- a/hearing-command/hearing-command-handler/pom.xml
+++ b/hearing-command/hearing-command-handler/pom.xml
@@ -92,8 +92,20 @@
${project.version}
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+ uk.gov.justice.utils
+ test-utils-logging-log4j
+ pom
+ test
+
+
uk.gov.justice.services
test-utils-core
diff --git a/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/HearingEventCommandHandler.java b/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/HearingEventCommandHandler.java
index 6303cff766..6331593358 100644
--- a/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/HearingEventCommandHandler.java
+++ b/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/HearingEventCommandHandler.java
@@ -3,21 +3,27 @@
import static java.util.UUID.fromString;
import static uk.gov.justice.services.core.annotation.Component.COMMAND_HANDLER;
+import uk.gov.justice.core.courts.HearingDay;
import uk.gov.justice.services.core.annotation.Handles;
import uk.gov.justice.services.core.annotation.ServiceComponent;
import uk.gov.justice.services.eventsourcing.source.core.exception.EventStreamException;
import uk.gov.justice.services.messaging.JsonEnvelope;
+import uk.gov.moj.cpp.hearing.command.handler.service.ReferenceDataService;
import uk.gov.moj.cpp.hearing.command.logEvent.CorrectLogEventCommand;
import uk.gov.moj.cpp.hearing.command.logEvent.CreateHearingEventDefinitionsCommand;
import uk.gov.moj.cpp.hearing.command.logEvent.LogEventCommand;
import uk.gov.moj.cpp.hearing.command.updateEvent.UpdateHearingEventsCommand;
+import uk.gov.moj.cpp.hearing.domain.CourtCentre;
import uk.gov.moj.cpp.hearing.domain.aggregate.HearingAggregate;
import uk.gov.moj.cpp.hearing.domain.aggregate.HearingEventDefinitionAggregate;
import uk.gov.moj.cpp.hearing.eventlog.HearingEvent;
+import java.time.ZonedDateTime;
import java.util.List;
+import java.util.Optional;
import java.util.UUID;
+import javax.inject.Inject;
import javax.json.JsonArray;
import javax.json.JsonObject;
@@ -38,6 +44,9 @@ public class HearingEventCommandHandler extends AbstractCommandHandler {
private static final UUID PAUSE_HEARING_EVENT_DEFINITION_ID = UUID.fromString("160ecb51-29ee-4954-bbbf-daab18a24fbb");
+ @Inject
+ private ReferenceDataService referenceDataService;
+
@Handles("hearing.create-hearing-event-definitions")
public void createHearingEventDefinitions(final JsonEnvelope jsonEnvelope) throws EventStreamException {
if (LOGGER.isDebugEnabled()) {
@@ -94,12 +103,14 @@ public void logHearingEvent(final JsonEnvelope jsonEnvelope) throws EventStreamE
.build();
final UUID activeHearingId = UUID.fromString(activeHearings.getString(index));
+ final CourtCentre pauseCourtCentre = resolveCourtCentre(activeHearingId, logEventCommand.getEventTime());
- aggregate(HearingAggregate.class, activeHearingId, jsonEnvelope, a -> a.logHearingEvent(activeHearingId, PAUSE_HEARING_EVENT_DEFINITION_ID, alterable, defenceCounselId, pauseHearingEvent, hearingTypeIds, userId));
+ aggregate(HearingAggregate.class, activeHearingId, jsonEnvelope, a -> a.logHearingEvent(activeHearingId, PAUSE_HEARING_EVENT_DEFINITION_ID, alterable, defenceCounselId, pauseHearingEvent, hearingTypeIds, userId, pauseCourtCentre));
}
}
- aggregate(HearingAggregate.class, logEventCommand.getHearingId(), jsonEnvelope, a -> a.logHearingEvent(hearingId, hearingEventDefinitionId, alterable, defenceCounselId, hearingEvent, hearingTypeIds, userId));
+ final CourtCentre courtCentre = resolveCourtCentre(hearingId, logEventCommand.getEventTime());
+ aggregate(HearingAggregate.class, logEventCommand.getHearingId(), jsonEnvelope, a -> a.logHearingEvent(hearingId, hearingEventDefinitionId, alterable, defenceCounselId, hearingEvent, hearingTypeIds, userId, courtCentre));
}
@Handles("hearing.command.update-hearing-events")
@@ -130,12 +141,33 @@ public void correctEvent(final JsonEnvelope jsonEnvelope) throws EventStreamExce
.withLastModifiedTime(correctLogEventCommand.getLastModifiedTime())
.withRecordedLabel(correctLogEventCommand.getRecordedLabel())
.withNote(correctLogEventCommand.getNote()).build();
+ final CourtCentre courtCentre = resolveCourtCentre(correctLogEventCommand.getHearingId(), correctLogEventCommand.getEventTime());
aggregate(HearingAggregate.class, correctLogEventCommand.getHearingId(), jsonEnvelope, a -> a.correctHearingEvent(correctLogEventCommand.getLatestHearingEventId(),
correctLogEventCommand.getHearingId(),
correctLogEventCommand.getHearingEventDefinitionId(),
correctLogEventCommand.getAlterable(),
correctLogEventCommand.getDefenceCounselId(),
hearingEvent,
- userId));
+ userId,
+ courtCentre));
+ }
+
+ private CourtCentre resolveCourtCentre(final UUID hearingId, final ZonedDateTime eventTime) {
+ try {
+ final HearingAggregate aggregate = aggregate(HearingAggregate.class, hearingId);
+ final Optional matchedDay = aggregate.findHearingDayFor(eventTime);
+ if (matchedDay.isEmpty()) {
+ return null;
+ }
+ final UUID centreId = matchedDay.get().getCourtCentreId();
+ final UUID roomId = matchedDay.get().getCourtRoomId();
+ if (centreId == null || roomId == null) {
+ return null;
+ }
+ return referenceDataService.resolveCourtCentre(centreId, roomId).orElse(null);
+ } catch (Exception e) {
+ LOGGER.warn("Failed to resolve court centre for hearing {} at {}; falling back to top-level", hearingId, eventTime, e);
+ return null;
+ }
}
}
diff --git a/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/ShareResultsCommandHandler.java b/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/ShareResultsCommandHandler.java
index b015a34d4e..6cbd41c254 100644
--- a/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/ShareResultsCommandHandler.java
+++ b/hearing-command/hearing-command-handler/src/main/java/uk/gov/moj/cpp/hearing/command/handler/ShareResultsCommandHandler.java
@@ -26,8 +26,13 @@
import uk.gov.moj.cpp.hearing.command.result.SharedResultsCommandResultLineV2;
import uk.gov.moj.cpp.hearing.command.result.UpdateDaysResultLinesStatusCommand;
import uk.gov.moj.cpp.hearing.command.result.UpdateResultLinesStatusCommand;
+import uk.gov.moj.cpp.hearing.command.handler.service.validation.ResultsValidator;
+import uk.gov.moj.cpp.hearing.command.handler.service.validation.ValidationRequest;
+import uk.gov.moj.cpp.hearing.command.handler.service.validation.ValidationRequestMapper;
+import uk.gov.moj.cpp.hearing.command.handler.service.validation.ValidationResponse;
import uk.gov.moj.cpp.hearing.domain.aggregate.ApplicationAggregate;
import uk.gov.moj.cpp.hearing.domain.aggregate.HearingAggregate;
+import uk.gov.moj.cpp.hearing.domain.event.result.ResultsValidationFailed;
import java.util.HashSet;
import java.util.List;
@@ -35,7 +40,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
@@ -56,6 +60,12 @@ public class ShareResultsCommandHandler extends AbstractCommandHandler {
@Inject
private ReferenceDataService referenceDataService;
+ @Inject
+ private ResultsValidator resultsValidationClient;
+
+ @Inject
+ private ValidationRequestMapper validationRequestMapper;
+
@Handles("hearing.command.save-draft-result")
public void saveDraftResult(final JsonEnvelope envelope) throws EventStreamException {
@@ -174,10 +184,28 @@ public void shareResultForDay(final JsonEnvelope envelope) throws EventStreamExc
}
final ShareDaysResultsCommand command = convertToObject(envelope, ShareDaysResultsCommand.class);
final UUID userId = envelope.metadata().userId().map(UUID::fromString).orElse(null);
+ long start = System.currentTimeMillis();
+ final EventStream eventStream = eventSource.getStreamById(command.getHearingId());
+ final HearingAggregate hearingAggregate = aggregateService.get(eventStream, HearingAggregate.class);
- aggregate(HearingAggregate.class, command.getHearingId(), envelope,
- aggregate -> shareDaysResultsEnrichedWithYouthCourt(aggregate, command, userId));
+ final ValidationRequest validationRequest = validationRequestMapper.toValidationRequest(command, hearingAggregate.getHearing());
+ final String userIdString = userId != null ? userId.toString() : "";
+
+ final ValidationResponse validationResponse = resultsValidationClient.validate(validationRequest, userIdString);
+ long end = System.currentTimeMillis();
+ LOGGER.info("Validation API call took {} ms for userId={} and for hearingId={}", end - start, userIdString, validationRequest.getHearingId());
+
+ if (validationResponse.hasErrors()) {
+ LOGGER.info("Share blocked by validation errors for hearing {}", command.getHearingId());
+ final ResultsValidationFailed failedEvent = buildValidationFailedEvent(command, userIdString, validationResponse);
+ eventStream.append(Stream.of(failedEvent).map(enveloper.withMetadataFrom(envelope)));
+ return;
+ }
+
+ eventStream.append(
+ shareDaysResultsEnrichedWithYouthCourt(hearingAggregate, command, userId)
+ .map(enveloper.withMetadataFrom(envelope)));
}
@Handles("hearing.command.update-result-lines-status")
@@ -215,6 +243,30 @@ public void replicateAllSharedResultsForHearing(final JsonEnvelope envelope) thr
aggregate -> aggregate.replicateSharedResultsForHearing(hearingId));
}
+ private ResultsValidationFailed buildValidationFailedEvent(final ShareDaysResultsCommand command,
+ final String userId,
+ final ValidationResponse validationResponse) {
+ return ResultsValidationFailed.builder()
+ .withHearingId(command.getHearingId())
+ .withHearingDay(command.getHearingDay())
+ .withUserId(userId)
+ .withErrors(validationResponse.getErrors().stream()
+ .map(e -> new ResultsValidationFailed.ValidationError(
+ e.getRuleId(), e.getSeverity(), e.getMessage(),
+ e.getAffectedOffences().stream()
+ .map(o -> o.getId())
+ .toList()))
+ .toList())
+ .withWarnings(validationResponse.getWarnings().stream()
+ .map(w -> new ResultsValidationFailed.ValidationError(
+ w.getRuleId(), w.getSeverity(), w.getMessage(),
+ w.getAffectedOffences().stream()
+ .map(o -> o.getId())
+ .toList()))
+ .toList())
+ .build();
+ }
+
private Stream