From b01463c7a409495ab7690241664544728f9f1f77 Mon Sep 17 00:00:00 2001 From: sstewart <202802468+scottstewart-sl@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:55:13 +0100 Subject: [PATCH 1/6] HDPI-6505: Change hearing PK to be a BIGINT/Long --- .../java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java | 2 +- src/main/resources/db/migration/V135__add_hearing_table.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java index 277cd2a6ea..8a727afb26 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java @@ -39,7 +39,7 @@ public class HearingEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; + private Long id; @ManyToOne(fetch = LAZY) @JoinColumn(name = "case_id") diff --git a/src/main/resources/db/migration/V135__add_hearing_table.sql b/src/main/resources/db/migration/V135__add_hearing_table.sql index 188eb430c6..f59b2b6e86 100644 --- a/src/main/resources/db/migration/V135__add_hearing_table.sql +++ b/src/main/resources/db/migration/V135__add_hearing_table.sql @@ -1,6 +1,6 @@ CREATE TABLE public.hearing ( - id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, case_id UUID REFERENCES pcs_case (id), type VARCHAR(60) NOT NULL, other_hearing_type VARCHAR(100), From 0995c3a2fa1b5eee4a490aaaad7e67adf816d5ea Mon Sep 17 00:00:00 2001 From: sstewart <202802468+scottstewart-sl@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:07:14 +0100 Subject: [PATCH 2/6] HDPI-6505: Ensure party list is hidden when turning off notice --- .../pcs/ccd/page/managehearing/AddHearingPage.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java index 43d5819422..1ebff3148d 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java @@ -4,8 +4,10 @@ import org.springframework.stereotype.Component; import uk.gov.hmcts.ccd.sdk.api.CaseDetails; import uk.gov.hmcts.ccd.sdk.api.callback.AboutToStartOrSubmitResponse; +import uk.gov.hmcts.reform.pcs.ccd.ShowConditions; import uk.gov.hmcts.reform.pcs.ccd.common.CcdPageConfiguration; import uk.gov.hmcts.reform.pcs.ccd.common.PageBuilder; +import uk.gov.hmcts.reform.pcs.ccd.domain.VerticalYesNo; import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; import uk.gov.hmcts.reform.pcs.ccd.domain.State; @@ -15,11 +17,17 @@ import java.util.List; import static uk.gov.hmcts.reform.pcs.ccd.ShowConditions.NEVER_SHOW; +import static uk.gov.hmcts.reform.pcs.ccd.ShowConditions.fieldEquals; @AllArgsConstructor @Component public class AddHearingPage implements CcdPageConfiguration, CcdPage { + private static final String PARTYLIST_SHOW_CONDITION = ShowConditions.and( + fieldEquals("hearing_IssueNotice", VerticalYesNo.YES), + fieldEquals("hearing_IsWithoutNotice", VerticalYesNo.YES) + ); + private final TextAreaValidationService textAreaValidationService; @Override @@ -53,7 +61,7 @@ public void addTo(PageBuilder pageBuilder) { .mandatory(Hearing::getIssueNotice) .mandatory(Hearing::getIsWithoutNotice, "hearing_IssueNotice=\"YES\"") .done() - .mandatory(PCSCase::getPartyMultiSelectionList, "hearing_IsWithoutNotice=\"YES\"", null, + .mandatory(PCSCase::getPartyMultiSelectionList, PARTYLIST_SHOW_CONDITION, null, "Who should receive the hearing notice?", "Select all that apply") .complex(PCSCase::getHearing) .optional(Hearing::getAdditionalInformation) From 54b81b4b9220c871ad5b73225f54671838564ef6 Mon Sep 17 00:00:00 2001 From: sstewart <202802468+scottstewart-sl@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:53:58 +0100 Subject: [PATCH 3/6] HDPI-6517: Cancel a hearing --- .../resources/cftlib-am-role-assignments.json | 17 ++ .../gov/hmcts/reform/pcs/ccd/PCSCaseView.java | 3 - .../hmcts/reform/pcs/ccd/domain/PCSCase.java | 3 - .../hmcts/reform/pcs/ccd/domain/State.java | 2 +- .../pcs/ccd/domain/hearing/Hearing.java | 13 ++ .../reform/pcs/ccd/entity/HearingEntity.java | 4 + .../hearing/ConfirmationBodyRenderer.java | 57 +++++ .../event/{ => hearing}/ManageHearing.java | 107 +++++++--- .../page/managehearing/AddHearingPage.java | 20 +- .../page/managehearing/CancelHearingPage.java | 38 +++- .../page/managehearing/ManageHearingPage.java | 10 +- .../pcs/ccd/repository/HearingRepository.java | 8 + .../service/hearing/DurationFormatter.java | 35 ++++ .../service/{ => hearing}/HearingService.java | 22 +- .../hearing/HearingSummaryRenderer.java | 66 ++++++ .../reform/pcs/ccd/view/HearingView.java | 42 ---- .../exception/HearingNotFoundException.java | 9 + .../V136__add_hearing_cancelled_flag.sql | 4 + .../hmcts/reform/pcs/ccd/PCSCaseViewTest.java | 6 +- .../pcs/ccd/event/ManageHearingTest.java | 195 ++++++++++++------ .../hearing/ConfirmationBodyRendererTest.java | 75 +++++++ .../managehearing/CancelHearingPageTest.java | 63 ++++++ .../pcs/ccd/service/HearingServiceTest.java | 65 +++++- .../hearing/DurationFormatterTest.java | 48 +++++ .../hearing/HearingSummaryRendererTest.java | 58 ++++++ .../reform/pcs/ccd/view/HearingViewTest.java | 69 ------- .../location-reference/court-venues.json | 4 +- 27 files changed, 797 insertions(+), 246 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRenderer.java rename src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/{ => hearing}/ManageHearing.java (62%) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/HearingRepository.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatter.java rename src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/{ => hearing}/HearingService.java (75%) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRenderer.java delete mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingView.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/exception/HearingNotFoundException.java create mode 100644 src/main/resources/db/migration/V136__add_hearing_cancelled_flag.sql create mode 100644 src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRendererTest.java create mode 100644 src/test/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPageTest.java create mode 100644 src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatterTest.java create mode 100644 src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRendererTest.java delete mode 100644 src/test/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingViewTest.java diff --git a/src/cftlib/resources/cftlib-am-role-assignments.json b/src/cftlib/resources/cftlib-am-role-assignments.json index a76894ca41..4a39a5402d 100644 --- a/src/cftlib/resources/cftlib-am-role-assignments.json +++ b/src/cftlib/resources/cftlib-am-role-assignments.json @@ -15,5 +15,22 @@ "authorisations": [] } ] + }, + { + "email": "caseworker@pcs.com", + "id": "74e702fa-e20f-3a40-bc1d-d915f0874d00", + "roleAssignments": [ + { + "roleType": "ORGANISATION", + "roleName": "hearing-centre-admin", + "grantType": "STANDARD", + "roleCategory": "LEGAL_OPERATIONS", + "classification": "PUBLIC", + "readOnly": false, + "attributes": { + }, + "authorisations": [] + } + ] } ] diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java index 05101c2780..8b0e6086bf 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseView.java @@ -33,7 +33,6 @@ import uk.gov.hmcts.reform.pcs.ccd.view.DocumentsView; import uk.gov.hmcts.reform.pcs.ccd.view.FeatureFlagView; import uk.gov.hmcts.reform.pcs.ccd.view.GenAppsView; -import uk.gov.hmcts.reform.pcs.ccd.view.HearingView; import uk.gov.hmcts.reform.pcs.ccd.view.NoticeOfPossessionView; import uk.gov.hmcts.reform.pcs.ccd.view.PartiesView; import uk.gov.hmcts.reform.pcs.ccd.view.RentArrearsView; @@ -91,7 +90,6 @@ public class PCSCaseView implements CaseView { private final CaseFlagsView flagsView; private final DefendantResponseView defendantResponseView; private final FeatureFlagView featureFlagView; - private final HearingView hearingView; /** * Invoked by CCD to load PCS cases by reference. @@ -173,7 +171,6 @@ private SubmittedCase getSubmittedCase(long caseReference) { caseListView.setCaseFields(pcsCase); defendantResponseView.setCaseFields(pcsCase, pcsCaseEntity); featureFlagView.setCaseFields(pcsCase); - hearingView.setCaseFields(pcsCase, pcsCaseEntity); return new SubmittedCase(pcsCase, pcsCaseEntity); } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java index aed5b65cf7..54288250d8 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java @@ -736,9 +736,6 @@ public class PCSCase { @CCD(searchable = false) private Hearing hearing; - @CCD(searchable = false) - private List> hearingList; - @CCD(searchable = false) private VerticalYesNo showManageHearingPage; diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java index 32aeec350c..4649d73063 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/State.java @@ -7,8 +7,8 @@ import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.CitizenAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.ClaimantAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.DefendantAccess; -import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.InternalCaseFlagAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.GlobalSearchAccess; +import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.InternalCaseFlagAccess; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.RasValidationAccess; /** diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/hearing/Hearing.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/hearing/Hearing.java index 151a7e8b67..390b64788c 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/hearing/Hearing.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/hearing/Hearing.java @@ -25,6 +25,10 @@ public class Hearing { public static final String NOTES_LABEL = "Hearing notes"; @CCD(ignore = true) public static final String ADDITIONAL_INFORMATION_LABEL = "Enter any additional information"; + @CCD(ignore = true) + public static final String CANCELLATION_REASON_LABEL = "Enter reason for cancellation"; + + private Long hearingId; @CCD(label = "Which type of hearing is this?") private HearingType type; @@ -78,4 +82,13 @@ public class Hearing { typeOverride = TextArea ) private String additionalInformation; + + @CCD( + label = CANCELLATION_REASON_LABEL, + hint = "This will be included in the notice sent to parties, if one is required", + typeOverride = TextArea + ) + private String cancellationReason; + + private String hearingSummaryMarkdown; } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java index 8a727afb26..b898845a7a 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/HearingEntity.java @@ -75,6 +75,10 @@ public class HearingEntity { @Builder.Default private List noticeParties = new ArrayList<>(); + private Boolean cancelled; + + private String cancellationReason; + public void addParty(UUID partyId) { noticeParties.add(partyId); } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRenderer.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRenderer.java new file mode 100644 index 0000000000..606c7ef451 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRenderer.java @@ -0,0 +1,57 @@ +package uk.gov.hmcts.reform.pcs.ccd.event.hearing; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.util.AddressFormatter; + +@Component +@RequiredArgsConstructor +public class ConfirmationBodyRenderer { + + private final AddressFormatter addressFormatter; + + public String renderHearingAddedConfirmationBody(PCSCase caseData, long caseReference) { + String address = getFormattedAddress(caseData); + String caseName = caseData.getCaseNameHmctsInternal(); + + return """ + --- +
+ Hearing added
+ Case number #%s
+ %s
+ %s
+
+ +

What happens next

+ + A hearing notice will be issued if you specified one is needed. + """.formatted(caseReference, address, caseName); + } + + public String renderHearingCancelledConfirmationBody(PCSCase caseData, long caseReference) { + String address = getFormattedAddress(caseData); + String caseName = caseData.getCaseNameHmctsInternal(); + + return """ + --- +
+ Hearing cancelled
+ Case number #%s
+ %s
+ %s
+
+ +

What happens next

+ + A cancellation notice will be sent to the parties. + """.formatted(caseReference, address, caseName); + } + + private String getFormattedAddress(PCSCase caseData) { + return addressFormatter + .formatMediumAddress(caseData.getPropertyAddress(), AddressFormatter.COMMA_DELIMITER); + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearing.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java similarity index 62% rename from src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearing.java rename to src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java index d9fbc62d1e..daeb1834c3 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearing.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java @@ -1,7 +1,8 @@ -package uk.gov.hmcts.reform.pcs.ccd.event; +package uk.gov.hmcts.reform.pcs.ccd.event.hearing; -import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import uk.gov.hmcts.ccd.sdk.api.CCDConfig; @@ -19,19 +20,23 @@ import uk.gov.hmcts.reform.pcs.ccd.domain.VerticalYesNo; import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.ManageHearingOption; import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.ClaimPartyEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyRole; import uk.gov.hmcts.reform.pcs.ccd.page.managehearing.ManageHearingConfigurer; -import uk.gov.hmcts.reform.pcs.ccd.service.HearingService; import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; +import uk.gov.hmcts.reform.pcs.ccd.service.hearing.HearingService; +import uk.gov.hmcts.reform.pcs.ccd.service.hearing.HearingSummaryRenderer; import uk.gov.hmcts.reform.pcs.ccd.service.party.PartyService; -import uk.gov.hmcts.reform.pcs.ccd.util.AddressFormatter; import uk.gov.hmcts.reform.pcs.location.model.CourtVenue; import uk.gov.hmcts.reform.pcs.location.service.LocationReferenceService; +import java.time.Clock; +import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -40,16 +45,36 @@ import static uk.gov.hmcts.reform.pcs.ccd.event.EventId.manageHearing; @Component -@AllArgsConstructor @Slf4j public class ManageHearing implements CCDConfig { private final ManageHearingConfigurer manageHearingConfigurer; - private final AddressFormatter addressFormatter; private final HearingService hearingService; private final LocationReferenceService locationReferenceService; private final PcsCaseService pcsCaseService; private final PartyService partyService; + private final HearingSummaryRenderer hearingSummaryRenderer; + private final ConfirmationBodyRenderer confirmationBodyRenderer; + private final Clock ukClock; + + public ManageHearing(ManageHearingConfigurer manageHearingConfigurer, + HearingService hearingService, + LocationReferenceService locationReferenceService, + PcsCaseService pcsCaseService, + PartyService partyService, + HearingSummaryRenderer hearingSummaryRenderer, + ConfirmationBodyRenderer confirmationBodyRenderer, + @Qualifier("ukClock") Clock ukClock) { + + this.manageHearingConfigurer = manageHearingConfigurer; + this.hearingService = hearingService; + this.locationReferenceService = locationReferenceService; + this.pcsCaseService = pcsCaseService; + this.partyService = partyService; + this.hearingSummaryRenderer = hearingSummaryRenderer; + this.confirmationBodyRenderer = confirmationBodyRenderer; + this.ukClock = ukClock; + } @Override public void configureDecentralised(DecentralisedConfigBuilder configBuilder) { @@ -90,10 +115,18 @@ private PCSCase start(EventPayload eventPayload) { pcsCase.setHearingLocation("Unable to find hearing location"); } - if (CollectionUtils.isEmpty(pcsCase.getHearingList())) { + List futureHearings = getFutureHearings(pcsCaseEntity); + + if (futureHearings.isEmpty()) { pcsCase.setShowManageHearingPage(VerticalYesNo.NO); pcsCase.setManageHearingOption(ManageHearingOption.ADD); } else { + HearingEntity nextHearingEntity = futureHearings.getFirst(); + String hearingLocation = pcsCase.getHearingLocation(); + pcsCase.getHearing().setHearingId(nextHearingEntity.getId()); + pcsCase.getHearing().setHearingSummaryMarkdown( + hearingSummaryRenderer.renderMarkdown(nextHearingEntity, hearingLocation)); + pcsCase.setShowManageHearingPage(VerticalYesNo.YES); } @@ -101,39 +134,38 @@ private PCSCase start(EventPayload eventPayload) { } private SubmitResponse submit(EventPayload eventPayload) { - Long caseId = eventPayload.caseReference(); + long caseReference = eventPayload.caseReference(); PCSCase caseData = eventPayload.caseData(); - String address = addressFormatter - .formatMediumAddress(caseData.getPropertyAddress(), AddressFormatter.COMMA_DELIMITER); - - if ( - caseData.getManageHearingOption() == ManageHearingOption.ADD - || caseData.getShowManageHearingPage() != VerticalYesNo.YES - ) { - hearingService.addHearing(caseId, caseData); + + // Default action is ADD if the choice screen wasn't shown + if (caseData.getShowManageHearingPage() != VerticalYesNo.YES) { + caseData.setManageHearingOption(ManageHearingOption.ADD); + } + + String confirmationBody = ""; + switch (caseData.getManageHearingOption()) { + case ADD: { + hearingService.addHearing(caseReference, caseData); + confirmationBody = confirmationBodyRenderer + .renderHearingAddedConfirmationBody(caseData, caseReference); + break; + } + case EDIT: { + break; + } + case CANCEL: { + hearingService.cancelHearing(caseData.getHearing()); + confirmationBody = confirmationBodyRenderer + .renderHearingCancelledConfirmationBody(caseData, caseReference); + break; + } } return SubmitResponse.builder() - .confirmationBody(getConfirmationBody(caseId, address, caseData.getCaseNameHmctsInternal())) + .confirmationBody(confirmationBody) .build(); } - private String getConfirmationBody(Long caseId, String address, String caseName) { - return """ - --- -
- Hearing Added
- Case number #%s
- %s
- %s
-
- -

What happens next

- - A hearing notice will be issued if you specified one is needed. - """.formatted(caseId, address, caseName); - } - private DynamicMultiSelectList buildPartyList(PcsCaseEntity pcsCaseEntity) { ClaimEntity mainClaim = pcsCaseEntity.getMainClaim(); Map> partyRoleListMap = mainClaim.getClaimParties().stream() @@ -162,4 +194,13 @@ private DynamicListElement mapToPartyListElement(ClaimEntity mainClaim, PartyEnt .build(); } + private List getFutureHearings(PcsCaseEntity pcsCaseEntity) { + LocalDateTime now = LocalDateTime.now(ukClock); + return pcsCaseEntity.getHearings().stream() + .filter(hearingEntity -> hearingEntity.getHearingDate().isAfter(now)) + .filter(hearingEntity -> BooleanUtils.isNotTrue(hearingEntity.getCancelled())) + .sorted(Comparator.comparing(HearingEntity::getHearingDate).reversed()) + .toList(); + } + } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java index 1ebff3148d..4f81f65011 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/AddHearingPage.java @@ -7,10 +7,12 @@ import uk.gov.hmcts.reform.pcs.ccd.ShowConditions; import uk.gov.hmcts.reform.pcs.ccd.common.CcdPageConfiguration; import uk.gov.hmcts.reform.pcs.ccd.common.PageBuilder; -import uk.gov.hmcts.reform.pcs.ccd.domain.VerticalYesNo; -import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; import uk.gov.hmcts.reform.pcs.ccd.domain.State; +import uk.gov.hmcts.reform.pcs.ccd.domain.VerticalYesNo; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.HearingType; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.ManageHearingOption; import uk.gov.hmcts.reform.pcs.ccd.page.CcdPage; import uk.gov.hmcts.reform.pcs.ccd.service.TextAreaValidationService; @@ -35,21 +37,21 @@ public void addTo(PageBuilder pageBuilder) { String pageKey = getPageKey(); pageBuilder .page(pageKey, this::midEvent) - .showCondition("manageHearingOption=\"ADD\"") + .showCondition(fieldEquals("manageHearingOption", ManageHearingOption.ADD)) .pageLabel("Add a hearing") .readonly(PCSCase::getHearingLocation, NEVER_SHOW) - .label("separator", "---") + .label(pageKey + "-separator", "---") .label( - "hearingLocationHeading", + pageKey + "-hearingLocationHeading", "

Hearing location:

" ) - .label("hearingLocationbody", "${hearingLocation}") + .label("-hearingLocationbody", "${hearingLocation}") .complex(PCSCase::getHearing) .mandatory(Hearing::getType) - .mandatory(Hearing::getOtherHearingType, "hearing_Type=\"OTHER\"") + .mandatory(Hearing::getOtherHearingType, fieldEquals("hearing_Type", HearingType.OTHER)) .mandatory(Hearing::getNoticeWording) .mandatory(Hearing::getDate) - .label("hearingDurationLabel", + .label(pageKey + "-hearingDurationLabel", """ How long will the hearing be? Enter duration @@ -59,7 +61,7 @@ public void addTo(PageBuilder pageBuilder) { .mandatory(Hearing::getDurationMinutes) .optional(Hearing::getNotes) .mandatory(Hearing::getIssueNotice) - .mandatory(Hearing::getIsWithoutNotice, "hearing_IssueNotice=\"YES\"") + .mandatory(Hearing::getIsWithoutNotice, fieldEquals("hearing_IssueNotice", VerticalYesNo.YES)) .done() .mandatory(PCSCase::getPartyMultiSelectionList, PARTYLIST_SHOW_CONDITION, null, "Who should receive the hearing notice?", "Select all that apply") diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPage.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPage.java index 928590a5e0..05a8a55dbb 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPage.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPage.java @@ -1,21 +1,40 @@ package uk.gov.hmcts.reform.pcs.ccd.page.managehearing; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import uk.gov.hmcts.ccd.sdk.api.CaseDetails; +import uk.gov.hmcts.ccd.sdk.api.callback.AboutToStartOrSubmitResponse; import uk.gov.hmcts.reform.pcs.ccd.common.CcdPageConfiguration; import uk.gov.hmcts.reform.pcs.ccd.common.PageBuilder; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.domain.State; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.ManageHearingOption; import uk.gov.hmcts.reform.pcs.ccd.page.CcdPage; +import uk.gov.hmcts.reform.pcs.ccd.service.TextAreaValidationService; + +import java.util.List; + +import static uk.gov.hmcts.reform.pcs.ccd.ShowConditions.fieldEquals; @Component +@RequiredArgsConstructor public class CancelHearingPage implements CcdPageConfiguration, CcdPage { + private final TextAreaValidationService textAreaValidationService; + @Override public void addTo(PageBuilder pageBuilder) { String pageKey = getPageKey(); pageBuilder - .page(pageKey) - .showCondition("manageHearingOption=\"CANCEL\"") + .page(pageKey, this::midEvent) + .showCondition(fieldEquals("manageHearingOption", ManageHearingOption.CANCEL)) .pageLabel("Cancel a hearing") - .label("placeholderCancelHearing", "PLACEHOLDER PAGE"); + .label(pageKey + "-separator", "---") + .complex(PCSCase::getHearing) + .label(pageKey + "-hearingSummary", "${hearing_HearingSummaryMarkdown}") + .mandatory(Hearing::getCancellationReason) + .done(); } @Override @@ -23,4 +42,17 @@ public String getPageKey() { return CcdPage.derivePageKey(this.getClass()); } + private AboutToStartOrSubmitResponse midEvent(CaseDetails details, + CaseDetails detailsBefore) { + PCSCase caseData = details.getData(); + Hearing hearing = caseData.getHearing(); + List validationErrors = textAreaValidationService.validateSingleTextArea( + hearing.getCancellationReason(), + Hearing.CANCELLATION_REASON_LABEL, + TextAreaValidationService.MEDIUM_TEXT_LIMIT + ); + + return textAreaValidationService.createValidationResponse(caseData, validationErrors); + } + } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/ManageHearingPage.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/ManageHearingPage.java index 18908c151c..9387df3f3c 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/ManageHearingPage.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/ManageHearingPage.java @@ -1,15 +1,18 @@ package uk.gov.hmcts.reform.pcs.ccd.page.managehearing; import org.springframework.stereotype.Component; +import uk.gov.hmcts.reform.pcs.ccd.ShowConditions; import uk.gov.hmcts.reform.pcs.ccd.common.CcdPageConfiguration; import uk.gov.hmcts.reform.pcs.ccd.common.PageBuilder; import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; import uk.gov.hmcts.reform.pcs.ccd.page.CcdPage; import static uk.gov.hmcts.reform.pcs.ccd.ShowConditions.NEVER_SHOW; @Component public class ManageHearingPage implements CcdPageConfiguration, CcdPage { + @Override public void addTo(PageBuilder pageBuilder) { String pageKey = getPageKey(); @@ -19,11 +22,16 @@ public void addTo(PageBuilder pageBuilder) { .readonly(PCSCase::getShowManageHearingPage, NEVER_SHOW) .showCondition("showManageHearingPage=\"YES\"") .label("manageHearingSeparator", "---") - .mandatory(PCSCase::getManageHearingOption); + .mandatory(PCSCase::getManageHearingOption) + .complex(PCSCase::getHearing) + .readonly(Hearing::getHearingId, ShowConditions.NEVER_SHOW, true) + .readonly(Hearing::getHearingSummaryMarkdown, ShowConditions.NEVER_SHOW, true) + .done(); } @Override public String getPageKey() { return CcdPage.derivePageKey(this.getClass()); } + } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/HearingRepository.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/HearingRepository.java new file mode 100644 index 0000000000..3f63250b2a --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/HearingRepository.java @@ -0,0 +1,8 @@ +package uk.gov.hmcts.reform.pcs.ccd.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; + +public interface HearingRepository extends JpaRepository { + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatter.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatter.java new file mode 100644 index 0000000000..637e24aac3 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatter.java @@ -0,0 +1,35 @@ +package uk.gov.hmcts.reform.pcs.ccd.service.hearing; + +import org.springframework.stereotype.Component; + +@Component +public class DurationFormatter { + + public String format(Integer hours, Integer minutes) { + + String result = ""; + + if (hours != null) { + if (hours == 1) { + result = "1 hour"; + } else { + result = "%d hours".formatted(hours); + } + } + + if (minutes != null) { + if (!result.isEmpty()) { + result += " "; + } + + if (minutes == 1) { + result += "1 minute"; + } else { + result += "%d minutes".formatted(minutes); + } + } + + return result; + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/HearingService.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingService.java similarity index 75% rename from src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/HearingService.java rename to src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingService.java index a700e79f0a..bfcdf2dd75 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/HearingService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingService.java @@ -1,4 +1,4 @@ -package uk.gov.hmcts.reform.pcs.ccd.service; +package uk.gov.hmcts.reform.pcs.ccd.service.hearing; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; @@ -10,7 +10,12 @@ import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.HearingType; import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; +import uk.gov.hmcts.reform.pcs.ccd.repository.HearingRepository; import uk.gov.hmcts.reform.pcs.ccd.repository.PcsCaseRepository; +import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; +import uk.gov.hmcts.reform.pcs.exception.HearingNotFoundException; + +import java.util.Objects; @Service @AllArgsConstructor @@ -18,6 +23,7 @@ public class HearingService { private final PcsCaseService pcsCaseService; private final PcsCaseRepository pcsCaseRepository; + private final HearingRepository hearingRepository; public void addHearing(long caseReference, PCSCase pcsCase) { PcsCaseEntity pcsCaseEntity = pcsCaseService.loadCase(caseReference); @@ -26,6 +32,20 @@ public void addHearing(long caseReference, PCSCase pcsCase) { pcsCaseRepository.save(pcsCaseEntity); } + public void cancelHearing(Hearing hearing) { + Long hearingId = Objects.requireNonNull(hearing.getHearingId(), "Hearing ID must be set"); + + HearingEntity hearingEntity = loadHearing(hearingId); + + hearingEntity.setCancelled(true); + hearingEntity.setCancellationReason(hearing.getCancellationReason()); + } + + private HearingEntity loadHearing(Long hearingId) { + return hearingRepository.findById(hearingId) + .orElseThrow(() -> new HearingNotFoundException("Hearing not found with ID " + hearingId)); + } + private HearingEntity createHearingEntity(PCSCase pcsCase) { Hearing hearing = pcsCase.getHearing(); HearingType hearingType = hearing.getType(); diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRenderer.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRenderer.java new file mode 100644 index 0000000000..9bcac206b7 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRenderer.java @@ -0,0 +1,66 @@ +package uk.gov.hmcts.reform.pcs.ccd.service.hearing; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.HearingType; +import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; + +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +@Service +@RequiredArgsConstructor +public class HearingSummaryRenderer { + + private static final DateTimeFormatter DATE_TIME_FORMATTER + = DateTimeFormatter.ofPattern("d MMMM yyyy, hh:mma", Locale.UK); + + private static final String TEMPLATE = """ +

You’re viewing the next scheduled hearing for this case.

+ +

Hearing location:

+

%s

+ +

Which type of hearing is this?

+

%s

+ +

When is the hearing?

+

%s

+ +

Hearing duration

+

%s

+ +

Hearing notes

+

%s

+ +

 

+ """; + + private final DurationFormatter durationFormatter; + + public String renderMarkdown(HearingEntity hearingEntity, String hearingLocation) { + + String hearingTypeLabel = getHearingTypeLabel(hearingEntity); + String formattedDate = DATE_TIME_FORMATTER.format(hearingEntity.getHearingDate()); + String formattedDuration = durationFormatter.format(hearingEntity.getDurationHours(), + hearingEntity.getDurationMinutes()); + + return TEMPLATE.formatted( + hearingLocation, + hearingTypeLabel, + formattedDate, + formattedDuration, + hearingEntity.getNotes() + ); + } + + private static String getHearingTypeLabel(HearingEntity hearingEntity) { + HearingType type = hearingEntity.getType(); + if (type != HearingType.OTHER) { + return type.getLabel(); + } else { + return "%s (%s)".formatted(type.getLabel(), hearingEntity.getOtherHearingType()); + } + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingView.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingView.java deleted file mode 100644 index 27ef74970d..0000000000 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingView.java +++ /dev/null @@ -1,42 +0,0 @@ -package uk.gov.hmcts.reform.pcs.ccd.view; - -import org.springframework.stereotype.Component; -import uk.gov.hmcts.ccd.sdk.type.ListValue; -import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; -import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; -import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; -import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; - -import java.util.List; - -@Component -public class HearingView { - - public void setCaseFields(PCSCase pcsCase, PcsCaseEntity pcsCaseEntity) { - setHearingFields(pcsCase, pcsCaseEntity.getHearings()); - } - - private void setHearingFields(PCSCase pcsCase, List hearingEntities) { - List> hearings = hearingEntities.stream().map(hearingEntity -> { - Hearing hearing = Hearing.builder() - .type(hearingEntity.getType()) - .otherHearingType(hearingEntity.getOtherHearingType()) - .noticeWording(hearingEntity.getNoticeWording()) - .date(hearingEntity.getHearingDate()) - .durationHours(hearingEntity.getDurationHours()) - .durationMinutes(hearingEntity.getDurationMinutes()) - .notes(hearingEntity.getNotes()) - .issueNotice(hearingEntity.getIssueNotice()) - .isWithoutNotice(hearingEntity.getIsWithoutNotice()) - .additionalInformation(hearingEntity.getAdditionalInformation()) - .build(); - - ListValue listValue = new ListValue<>(); - listValue.setValue(hearing); - - return listValue; - }).toList(); - - pcsCase.setHearingList(hearings); - } -} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/exception/HearingNotFoundException.java b/src/main/java/uk/gov/hmcts/reform/pcs/exception/HearingNotFoundException.java new file mode 100644 index 0000000000..72b579045e --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/exception/HearingNotFoundException.java @@ -0,0 +1,9 @@ +package uk.gov.hmcts.reform.pcs.exception; + +public class HearingNotFoundException extends RuntimeException { + + public HearingNotFoundException(String message) { + super(message); + } + +} diff --git a/src/main/resources/db/migration/V136__add_hearing_cancelled_flag.sql b/src/main/resources/db/migration/V136__add_hearing_cancelled_flag.sql new file mode 100644 index 0000000000..bf7765f9a6 --- /dev/null +++ b/src/main/resources/db/migration/V136__add_hearing_cancelled_flag.sql @@ -0,0 +1,4 @@ +ALTER TABLE hearing + ADD COLUMN cancelled BOOLEAN, + ADD COLUMN cancellation_reason VARCHAR(500); + diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseViewTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseViewTest.java index 6f086681fa..85803ca9a9 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseViewTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/PCSCaseViewTest.java @@ -36,7 +36,6 @@ import uk.gov.hmcts.reform.pcs.ccd.view.DocumentsView; import uk.gov.hmcts.reform.pcs.ccd.view.FeatureFlagView; import uk.gov.hmcts.reform.pcs.ccd.view.GenAppsView; -import uk.gov.hmcts.reform.pcs.ccd.view.HearingView; import uk.gov.hmcts.reform.pcs.ccd.view.NoticeOfPossessionView; import uk.gov.hmcts.reform.pcs.ccd.view.PartiesView; import uk.gov.hmcts.reform.pcs.ccd.view.RentArrearsView; @@ -131,8 +130,6 @@ class PCSCaseViewTest { private DefendantResponseView defendantResponseView; @Mock private FeatureFlagView featureFlagView; - @Mock - private HearingView hearingView; private PCSCaseView underTest; @@ -148,7 +145,7 @@ void setUp() { statementOfTruthView, caseFieldsView, searchCriteriaIndexer, caseListView, caseLinkView, enforcementOrderMediator, caseNoteView, caseTabView, partiesView, genAppsView, caseFlagsView, - defendantResponseView, featureFlagView, hearingView + defendantResponseView, featureFlagView ); } @@ -321,7 +318,6 @@ void shouldSetCaseFieldsInViewHelpers() { verify(caseListView).setCaseFields(pcsCase); verify(genAppsView).setCaseFields(pcsCase, pcsCaseEntity); verify(featureFlagView).setCaseFields(pcsCase); - verify(hearingView).setCaseFields(pcsCase, pcsCaseEntity); } @Test diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java index 8ae3c81cc2..dd41c1cc24 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java @@ -5,6 +5,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -13,7 +16,6 @@ import uk.gov.hmcts.ccd.sdk.type.CaseLocation; import uk.gov.hmcts.ccd.sdk.type.DynamicListElement; import uk.gov.hmcts.ccd.sdk.type.DynamicMultiSelectList; -import uk.gov.hmcts.ccd.sdk.type.ListValue; import uk.gov.hmcts.reform.pcs.ccd.common.PageBuilder; import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; import uk.gov.hmcts.reform.pcs.ccd.domain.State; @@ -21,38 +23,48 @@ import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.ManageHearingOption; import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.ClaimPartyEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyRole; +import uk.gov.hmcts.reform.pcs.ccd.event.hearing.ConfirmationBodyRenderer; +import uk.gov.hmcts.reform.pcs.ccd.event.hearing.ManageHearing; import uk.gov.hmcts.reform.pcs.ccd.page.managehearing.ManageHearingConfigurer; -import uk.gov.hmcts.reform.pcs.ccd.service.HearingService; import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; +import uk.gov.hmcts.reform.pcs.ccd.service.hearing.HearingService; +import uk.gov.hmcts.reform.pcs.ccd.service.hearing.HearingSummaryRenderer; import uk.gov.hmcts.reform.pcs.ccd.service.party.PartyService; -import uk.gov.hmcts.reform.pcs.ccd.util.AddressFormatter; import uk.gov.hmcts.reform.pcs.location.model.CourtVenue; import uk.gov.hmcts.reform.pcs.location.service.LocationReferenceService; +import java.time.Clock; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import java.util.stream.Stream; +import static java.time.Month.JULY; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static uk.gov.hmcts.reform.pcs.config.ClockConfiguration.UK_ZONE_ID; @ExtendWith(MockitoExtension.class) public class ManageHearingTest extends BaseEventTest { private static final int BASE_LOCATION_ID = 1; + private static final LocalDateTime FIXED_TEST_TIME = LocalDate.of(2026, JULY, 10).atTime(10, 20); @Mock private ManageHearingConfigurer manageHearingConfigurer; @Mock - private AddressFormatter addressFormatter; - @Mock private HearingService hearingService; @Mock private LocationReferenceService locationReferenceService; @@ -61,16 +73,28 @@ public class ManageHearingTest extends BaseEventTest { @Mock private PartyService partyService; @Mock + private HearingSummaryRenderer hearingSummaryRenderer; + @Mock + private ConfirmationBodyRenderer confirmationBodyRenderer; + @Mock private PcsCaseEntity pcsCaseEntity; @Mock private ClaimEntity mainClaim; + @Mock(strictness = LENIENT) + private Clock ukClock; @InjectMocks - private ManageHearing manageHearing; + private ManageHearing underTest; @BeforeEach void setUp() { - setEventUnderTest(manageHearing); + when(ukClock.instant()).thenReturn(FIXED_TEST_TIME.atZone(UK_ZONE_ID).toInstant()); + when(ukClock.getZone()).thenReturn(UK_ZONE_ID); + + underTest = new ManageHearing(manageHearingConfigurer, hearingService, locationReferenceService, + pcsCaseService, partyService, hearingSummaryRenderer, confirmationBodyRenderer, + ukClock); + setEventUnderTest(underTest); } @Test @@ -99,41 +123,57 @@ void setUp() { when(caseLocation.getBaseLocation()).thenReturn(Integer.toString(BASE_LOCATION_ID)); } - @Test - void shouldShowManageHearingPageIfHearingExists() { + @ParameterizedTest + @MethodSource("hearingScenarios") + void shouldSetShowManageHearingFlags(List hearingEntities, + VerticalYesNo expectedShowManageHearingPage, + ManageHearingOption expectedManageHearingOption) { // Given - Hearing hearing = Hearing.builder().build(); - List> hearingList = List.of( - ListValue.builder().value(hearing).build() - ); - PCSCase pcsCase = PCSCase.builder() .caseManagementLocation(caseLocation) - .hearingList(hearingList) + .hearing(new Hearing()) .build(); + when(pcsCaseService.loadCase(TEST_CASE_REFERENCE)).thenReturn(pcsCaseEntity); + when(pcsCaseEntity.getHearings()).thenReturn(hearingEntities); + // When PCSCase response = callStartHandler(pcsCase); // Then - assertThat(response.getShowManageHearingPage()).isEqualTo(VerticalYesNo.YES); - assertThat(response.getManageHearingOption()).isNull(); + assertThat(response.getShowManageHearingPage()).isEqualTo(expectedShowManageHearingPage); + assertThat(response.getManageHearingOption()).isEqualTo(expectedManageHearingOption); } - @Test - void shouldPreselectAddActionAndNotShowManageHearingPageIfHearingDoesNotExists() { - // Given - PCSCase pcsCase = PCSCase.builder() - .caseManagementLocation(caseLocation) - .hearingList(List.of()) - .build(); - - // When - PCSCase response = callStartHandler(pcsCase); - - // Then - assertThat(response.getShowManageHearingPage()).isEqualTo(VerticalYesNo.NO); - assertThat(response.getManageHearingOption()).isEqualTo(ManageHearingOption.ADD); + private static Stream hearingScenarios() { + return Stream.of( + arguments( + List.of(), + VerticalYesNo.NO, + ManageHearingOption.ADD), + arguments( + List.of(createHearing(FIXED_TEST_TIME.minusSeconds(1))), + VerticalYesNo.NO, + ManageHearingOption.ADD + ), + arguments( + List.of(createCancelledHearing(FIXED_TEST_TIME.plusSeconds(1))), + VerticalYesNo.NO, + ManageHearingOption.ADD + ), + arguments( + List.of(createHearing(FIXED_TEST_TIME.plusSeconds(1))), + VerticalYesNo.YES, + null + ), + arguments( + List.of( + createCancelledHearing(FIXED_TEST_TIME.plusSeconds(1)), + createHearing(FIXED_TEST_TIME.plusSeconds(2))), + VerticalYesNo.YES, + null + ) + ); } @Test @@ -182,9 +222,7 @@ void shouldSetHearingLocation() { .caseManagementLocation(caseLocation) .build(); - CourtVenue courtVenue = mock(CourtVenue.class); - when(locationReferenceService.getCourtVenues(List.of(BASE_LOCATION_ID))).thenReturn(List.of(courtVenue)); - when(courtVenue.courtName()).thenReturn(expectedHearingLocation); + stubHearingLocation(expectedHearingLocation); // When PCSCase response = callStartHandler(pcsCase); @@ -193,6 +231,33 @@ void shouldSetHearingLocation() { assertThat(response.getHearingLocation()).isEqualTo(expectedHearingLocation); } + @Test + void shouldSetHearingSummaryMarkdown() { + // Given + final String hearingLocation = "Hearing location name"; + final String expectedHearingSummaryMarkdown = "some hearing summary markdown"; + + stubHearingLocation(hearingLocation); + + HearingEntity hearingEntity = createHearing(FIXED_TEST_TIME.plusSeconds(1)); + + when(pcsCaseService.loadCase(TEST_CASE_REFERENCE)).thenReturn(pcsCaseEntity); + when(pcsCaseEntity.getHearings()).thenReturn(List.of(hearingEntity)); + when(hearingSummaryRenderer.renderMarkdown(hearingEntity, hearingLocation)) + .thenReturn(expectedHearingSummaryMarkdown); + + PCSCase pcsCase = PCSCase.builder() + .caseManagementLocation(caseLocation) + .hearing(new Hearing()) + .build(); + + // When + PCSCase response = callStartHandler(pcsCase); + + // Then + assertThat(response.getHearing().getHearingSummaryMarkdown()).isEqualTo(expectedHearingSummaryMarkdown); + } + @Test void shouldHandleExceptionGettingHearingVenue() { // Given @@ -225,6 +290,25 @@ void shouldHandleNoResultGettingHearingVenue() { assertThat(response.getHearingLocation()).isEqualTo("Unable to find hearing location"); } + private static HearingEntity createHearing(LocalDateTime hearingDate) { + return HearingEntity.builder() + .hearingDate(hearingDate) + .build(); + } + + private static HearingEntity createCancelledHearing(LocalDateTime hearingDate) { + return HearingEntity.builder() + .hearingDate(hearingDate) + .cancelled(true) + .build(); + } + + private void stubHearingLocation(String expectedHearingLocation) { + CourtVenue courtVenue = mock(CourtVenue.class); + when(locationReferenceService.getCourtVenues(List.of(BASE_LOCATION_ID))).thenReturn(List.of(courtVenue)); + when(courtVenue.courtName()).thenReturn(expectedHearingLocation); + } + } @Nested @@ -242,29 +326,16 @@ void shouldAddHearingOnSubmit() { .caseNameHmctsInternal("Claimant v Defendant") .build(); - when(addressFormatter.formatMediumAddress(address, AddressFormatter.COMMA_DELIMITER)) - .thenReturn("address"); + String expectedConfirmationBody = "confirmation body"; + when(confirmationBodyRenderer.renderHearingAddedConfirmationBody(pcsCase, TEST_CASE_REFERENCE)) + .thenReturn(expectedConfirmationBody); // When SubmitResponse submitResponse = callSubmitHandler(pcsCase); // Then verify(hearingService).addHearing(TEST_CASE_REFERENCE, pcsCase); - assertThat(submitResponse.getConfirmationBody()).isEqualTo( - """ - --- -
- Hearing Added
- Case number #%s
- address
- Claimant v Defendant
-
- -

What happens next

- - A hearing notice will be issued if you specified one is needed. - """.formatted(TEST_CASE_REFERENCE) - ); + assertThat(submitResponse.getConfirmationBody()).isEqualTo(expectedConfirmationBody); } @Test @@ -275,31 +346,19 @@ void shouldAddHearingOnSubmitWhenMangeHearingPageWasNotShown() { PCSCase pcsCase = PCSCase.builder() .propertyAddress(address) .caseNameHmctsInternal("Claimant v Defendant") + .showManageHearingPage(VerticalYesNo.NO) .build(); - when(addressFormatter.formatMediumAddress(address, AddressFormatter.COMMA_DELIMITER)) - .thenReturn("address"); + String expectedConfirmationBody = "confirmation body"; + when(confirmationBodyRenderer.renderHearingAddedConfirmationBody(pcsCase, TEST_CASE_REFERENCE)) + .thenReturn(expectedConfirmationBody); // When SubmitResponse submitResponse = callSubmitHandler(pcsCase); // Then verify(hearingService).addHearing(TEST_CASE_REFERENCE, pcsCase); - assertThat(submitResponse.getConfirmationBody()).isEqualTo( - """ - --- -
- Hearing Added
- Case number #1234
- address
- Claimant v Defendant
-
- -

What happens next

- - A hearing notice will be issued if you specified one is needed. - """ - ); + assertThat(submitResponse.getConfirmationBody()).isEqualTo(expectedConfirmationBody); } @Test diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRendererTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRendererTest.java new file mode 100644 index 0000000000..b3bb94310c --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ConfirmationBodyRendererTest.java @@ -0,0 +1,75 @@ +package uk.gov.hmcts.reform.pcs.ccd.event.hearing; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.hmcts.ccd.sdk.type.AddressUK; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.util.AddressFormatter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ConfirmationBodyRendererTest { + + private static final long CASE_REFERENCE = 1234L; + + @Mock + private AddressFormatter addressFormatter; + @Mock + private AddressUK propertyAddress; + + private ConfirmationBodyRenderer underTest; + + @BeforeEach + void setUp() { + underTest = new ConfirmationBodyRenderer(addressFormatter); + } + + @Test + void shouldRenderAddHearingConfirmationBody() { + // Given + PCSCase caseData = PCSCase.builder() + .propertyAddress(propertyAddress) + .caseNameHmctsInternal("internal case name") + .build(); + + when(addressFormatter.formatMediumAddress(propertyAddress, AddressFormatter.COMMA_DELIMITER)) + .thenReturn("formatted property address"); + + // When + String confirmationBody = underTest.renderHearingAddedConfirmationBody(caseData, CASE_REFERENCE); + + // Then + assertThat(confirmationBody) + .contains("Hearing added") + .contains("Case number #1234") + .contains("formatted property address") + .contains("internal case name"); + } + + @Test + void shouldRenderCancelHearingConfirmationBody() { + // Given + PCSCase caseData = PCSCase.builder() + .propertyAddress(propertyAddress) + .caseNameHmctsInternal("internal case name") + .build(); + + when(addressFormatter.formatMediumAddress(propertyAddress, AddressFormatter.COMMA_DELIMITER)) + .thenReturn("formatted property address"); + + // When + String confirmationBody = underTest.renderHearingCancelledConfirmationBody(caseData, CASE_REFERENCE); + + // Then + assertThat(confirmationBody) + .contains("Hearing cancelled") + .contains("Case number #1234") + .contains("formatted property address") + .contains("internal case name"); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPageTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPageTest.java new file mode 100644 index 0000000000..1d18385843 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/page/managehearing/CancelHearingPageTest.java @@ -0,0 +1,63 @@ +package uk.gov.hmcts.reform.pcs.ccd.page.managehearing; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.hmcts.ccd.sdk.api.callback.AboutToStartOrSubmitResponse; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.domain.State; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; +import uk.gov.hmcts.reform.pcs.ccd.page.BasePageTest; +import uk.gov.hmcts.reform.pcs.ccd.service.TextAreaValidationService; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CancelHearingPageTest extends BasePageTest { + + @Mock + private TextAreaValidationService textAreaValidationService; + + @BeforeEach + void setUp() { + CancelHearingPage cancelHearingPage = new CancelHearingPage(textAreaValidationService); + setPageUnderTest(cancelHearingPage); + } + + @Test + void shouldValidateTextAreaLength() { + // Given + String cancellationReason = "some cancellation reason"; + PCSCase caseData = PCSCase.builder() + .hearing(Hearing.builder() + .cancellationReason(cancellationReason) + .build()) + .build(); + + List validationErrors = List.of("Error 1", "Error 2"); + when(textAreaValidationService + .validateSingleTextArea(cancellationReason, "Enter reason for cancellation", 500)) + .thenReturn(validationErrors); + + AboutToStartOrSubmitResponse expectedResponse = createMockResponse(); + when(textAreaValidationService.createValidationResponse(caseData, validationErrors)) + .thenReturn(expectedResponse); + + // When + AboutToStartOrSubmitResponse actualResponse = callMidEventHandler(caseData); + + // Then + assertThat(actualResponse).isEqualTo(expectedResponse); + } + + @SuppressWarnings("unchecked") + private static AboutToStartOrSubmitResponse createMockResponse() { + return mock(AboutToStartOrSubmitResponse.class); + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/HearingServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/HearingServiceTest.java index 281adf6f0f..292fa30c8d 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/HearingServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/HearingServiceTest.java @@ -1,9 +1,9 @@ package uk.gov.hmcts.reform.pcs.ccd.service; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import uk.gov.hmcts.ccd.sdk.type.DynamicListElement; @@ -15,13 +15,19 @@ import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.HearingType; import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; +import uk.gov.hmcts.reform.pcs.ccd.repository.HearingRepository; import uk.gov.hmcts.reform.pcs.ccd.repository.PcsCaseRepository; +import uk.gov.hmcts.reform.pcs.ccd.service.hearing.HearingService; +import uk.gov.hmcts.reform.pcs.exception.HearingNotFoundException; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,12 +36,17 @@ public class HearingServiceTest { @Mock private PcsCaseService pcsCaseService; - @Mock private PcsCaseRepository pcsCaseRepository; + @Mock + private HearingRepository hearingRepository; - @InjectMocks - private HearingService hearingService; + private HearingService underTest; + + @BeforeEach + void setUp() { + underTest = new HearingService(pcsCaseService, pcsCaseRepository, hearingRepository); + } @Test void shouldSaveHearing() { @@ -73,7 +84,7 @@ void shouldSaveHearing() { when(pcsCaseService.loadCase(caseReference)).thenReturn(pcsCaseEntity); // When - hearingService.addHearing(caseReference, pcsCase); + underTest.addHearing(caseReference, pcsCase); ArgumentCaptor pcsCaseEntityCaptor = ArgumentCaptor.forClass(PcsCaseEntity.class); verify(pcsCaseRepository).save(pcsCaseEntityCaptor.capture()); @@ -133,7 +144,7 @@ void shouldNotSavePartiesIfIsWithoutNotice() { when(pcsCaseService.loadCase(caseReference)).thenReturn(pcsCaseEntity); // When - hearingService.addHearing(caseReference, pcsCase); + underTest.addHearing(caseReference, pcsCase); ArgumentCaptor pcsCaseEntityCaptor = ArgumentCaptor.forClass(PcsCaseEntity.class); verify(pcsCaseRepository).save(pcsCaseEntityCaptor.capture()); @@ -155,4 +166,46 @@ void shouldNotSavePartiesIfIsWithoutNotice() { assertThat(hearingEntity.getAdditionalInformation()).isEqualTo("additional information"); assertThat(hearingEntity.getNoticeParties()).isEmpty(); } + + @Test + void shouldCancelHearing() { + // Given + long hearingId = 5678L; + String expectedCancellationReason = "some cancellation reason"; + + Hearing hearing = Hearing.builder() + .hearingId(hearingId) + .cancellationReason(expectedCancellationReason) + .build(); + + HearingEntity hearingEntity = mock(HearingEntity.class); + when(hearingRepository.findById(hearingId)).thenReturn(Optional.of(hearingEntity)); + + // When + underTest.cancelHearing(hearing); + + // Then + verify(hearingEntity).setCancelled(true); + verify(hearingEntity).setCancellationReason(expectedCancellationReason); + } + + @Test + void shouldThrowExceptionCancellingUnknownHearing() { + // Given + long hearingId = 5678L; + + Hearing hearing = Hearing.builder() + .hearingId(hearingId) + .build(); + + when(hearingRepository.findById(hearingId)).thenReturn(Optional.empty()); + + // When + Throwable throwable = catchThrowable(() -> underTest.cancelHearing(hearing)); + + // Then + assertThat(throwable) + .isInstanceOf(HearingNotFoundException.class) + .hasMessage("Hearing not found with ID %d", hearingId); + } } diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatterTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatterTest.java new file mode 100644 index 0000000000..be6cd3d466 --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/DurationFormatterTest.java @@ -0,0 +1,48 @@ +package uk.gov.hmcts.reform.pcs.ccd.service.hearing; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class DurationFormatterTest { + + private DurationFormatter underTest; + + @BeforeEach + void setUp() { + underTest = new DurationFormatter(); + } + + @ParameterizedTest + @MethodSource("hoursAndMinutesScenarios") + void shouldFormatHoursAndMinutes(Integer hours, Integer minutes, String expectedFormattedDuration) { + // When + String actualFormattedDuration = underTest.format(hours, minutes); + + // Then + assertThat(actualFormattedDuration).isEqualTo(expectedFormattedDuration); + } + + private static Stream hoursAndMinutesScenarios() { + return Stream.of( + Arguments.arguments(0, 0, "0 hours 0 minutes"), + Arguments.arguments(0, 1, "0 hours 1 minute"), + Arguments.arguments(0, 2, "0 hours 2 minutes"), + Arguments.arguments(1, 0, "1 hour 0 minutes"), + Arguments.arguments(2, 0, "2 hours 0 minutes"), + Arguments.arguments(null, 0, "0 minutes"), + Arguments.arguments(0, null, "0 hours"), + Arguments.arguments(null, 1, "1 minute"), + Arguments.arguments(1, null, "1 hour"), + Arguments.arguments(null, null, "") + ); + } + + + +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRendererTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRendererTest.java new file mode 100644 index 0000000000..03bde84f4e --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/hearing/HearingSummaryRendererTest.java @@ -0,0 +1,58 @@ +package uk.gov.hmcts.reform.pcs.ccd.service.hearing; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.HearingType; +import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class HearingSummaryRendererTest { + + @Mock + private DurationFormatter durationFormatter; + + private HearingSummaryRenderer underTest; + + @BeforeEach + void setUp() { + underTest = new HearingSummaryRenderer(durationFormatter); + } + + @Test + void shouldRenderHearingSummary() { + // Given + HearingEntity hearingEntity = HearingEntity.builder() + .type(HearingType.OTHER) + .otherHearingType("some other hearing type") + .hearingDate(LocalDateTime.parse("2026-07-10T09:00:00")) + .durationHours(1) + .durationMinutes(30) + .notes("some notes") + .build(); + + String hearingLocation = "some hearing location"; + + when(durationFormatter.format(1, 30)).thenReturn("formatted duration"); + + // When + String hearingSummary = underTest.renderMarkdown(hearingEntity, hearingLocation); + + // Then + assertThat(hearingSummary) + .contains("

some hearing location

") + .contains("

Other (some other hearing type)

") + .contains("

10 July 2026, 09:00am

") + .contains("

formatted duration

") + .contains("

some notes

"); + + } + +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingViewTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingViewTest.java deleted file mode 100644 index f3ee79c7a9..0000000000 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/view/HearingViewTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package uk.gov.hmcts.reform.pcs.ccd.view; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import uk.gov.hmcts.ccd.sdk.type.ListValue; -import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.Hearing; -import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.HearingNoticeWording; -import uk.gov.hmcts.reform.pcs.ccd.domain.hearing.HearingType; -import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; -import uk.gov.hmcts.reform.pcs.ccd.domain.VerticalYesNo; -import uk.gov.hmcts.reform.pcs.ccd.entity.HearingEntity; -import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; - -import java.time.LocalDateTime; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -public class HearingViewTest { - - private HearingView hearingView; - - @BeforeEach - void setUp() { - hearingView = new HearingView(); - } - - @Test - void shouldMapHearingEntityToHearing() { - // Given - HearingEntity hearingEntity = HearingEntity.builder() - .type(HearingType.OTHER) - .otherHearingType("other type") - .noticeWording(HearingNoticeWording.ADJ) - .hearingDate(LocalDateTime.of(2026, 2, 1, 9, 0, 0)) - .durationMinutes(30) - .durationHours(1) - .notes("notes") - .issueNotice(VerticalYesNo.YES) - .isWithoutNotice(VerticalYesNo.NO) - .additionalInformation("additional information") - .build(); - - PcsCaseEntity pcsCaseEntity = PcsCaseEntity.builder() - .hearings(List.of(hearingEntity)) - .build(); - - PCSCase pcsCase = PCSCase.builder().build(); - - // When - hearingView.setCaseFields(pcsCase, pcsCaseEntity); - - // Then - List> hearings = pcsCase.getHearingList(); - assertThat(hearings).hasSize(1); - - Hearing hearing = hearings.getFirst().getValue(); - assertThat(hearing.getType()).isEqualTo(HearingType.OTHER); - assertThat(hearing.getOtherHearingType()).isEqualTo("other type"); - assertThat(hearing.getNoticeWording()).isEqualTo(HearingNoticeWording.ADJ); - assertThat(hearing.getDate()).isEqualTo(LocalDateTime.of(2026, 2, 1, 9, 0, 0)); - assertThat(hearing.getDurationMinutes()).isEqualTo(30); - assertThat(hearing.getDurationHours()).isEqualTo(1); - assertThat(hearing.getNotes()).isEqualTo("notes"); - assertThat(hearing.getIssueNotice()).isEqualTo(VerticalYesNo.YES); - assertThat(hearing.getIsWithoutNotice()).isEqualTo(VerticalYesNo.NO); - assertThat(hearing.getAdditionalInformation()).isEqualTo("additional information"); - } -} diff --git a/wiremock/mappings/location-reference/court-venues.json b/wiremock/mappings/location-reference/court-venues.json index 16e2538ea5..1269e9d92a 100644 --- a/wiremock/mappings/location-reference/court-venues.json +++ b/wiremock/mappings/location-reference/court-venues.json @@ -22,7 +22,7 @@ "jsonBody": [ { "court_location_id": "123", - "court_location_name": "54 HAGLEY ROAD (BIRMINGHAM OFFICES)", + "court_name": "54 HAGLEY ROAD (BIRMINGHAM OFFICES)", "epims_id": "{{request.query.epimms_id}}", "open_for_public": "YES", "court_location_category": "North", @@ -36,7 +36,7 @@ "phone_number": "B168PE", "court_location_code": "", "dx_address": "HAGLEY ROAD", - "welsh_court_location_name": "", + "welsh_court_name": "", "welsh_court_address": "" } ] From c62548f2449cf528ce51d7a5bab13a9476fa68d3 Mon Sep 17 00:00:00 2001 From: sstewart <202802468+scottstewart-sl@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:36:17 +0100 Subject: [PATCH 4/6] HDPI-6517: Fix event roles and add guard for unknown case management location --- .../ccd/accesscontrol/CaseworkerRoles.java | 12 +++++ .../pcs/ccd/event/hearing/ManageHearing.java | 48 ++++++++++++------- .../pcs/ccd/event/ManageHearingTest.java | 15 +++++- 3 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/CaseworkerRoles.java diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/CaseworkerRoles.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/CaseworkerRoles.java new file mode 100644 index 0000000000..c278000f1e --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/accesscontrol/CaseworkerRoles.java @@ -0,0 +1,12 @@ +package uk.gov.hmcts.reform.pcs.ccd.accesscontrol; + +public final class CaseworkerRoles { + + public static final UserRole[] CASEWORKER_ROLES = { + UserRole.HEARING_CENTRE_TEAM_LEADER, + UserRole.HEARING_CENTRE_ADMIN, + }; + + private CaseworkerRoles() { + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java index daeb1834c3..82a64d884e 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java @@ -11,6 +11,7 @@ import uk.gov.hmcts.ccd.sdk.api.EventPayload; import uk.gov.hmcts.ccd.sdk.api.Permission; import uk.gov.hmcts.ccd.sdk.api.callback.SubmitResponse; +import uk.gov.hmcts.ccd.sdk.type.CaseLocation; import uk.gov.hmcts.ccd.sdk.type.DynamicListElement; import uk.gov.hmcts.ccd.sdk.type.DynamicMultiSelectList; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.UserRole; @@ -41,6 +42,7 @@ import java.util.Map; import java.util.stream.Collectors; +import static uk.gov.hmcts.reform.pcs.ccd.accesscontrol.CaseworkerRoles.CASEWORKER_ROLES; import static uk.gov.hmcts.reform.pcs.ccd.accesscontrol.JudicialHistoryRoles.JUDICIAL_HISTORY_ROLES; import static uk.gov.hmcts.reform.pcs.ccd.event.EventId.manageHearing; @@ -82,7 +84,7 @@ public void configureDecentralised(DecentralisedConfigBuilder eventPayload) { pcsCase.setPartyMultiSelectionList(buildPartyList(pcsCaseEntity)); - List baseLocation = List.of(Integer.parseInt(pcsCase.getCaseManagementLocation().getBaseLocation())); - - try { - List courtVenues = locationReferenceService.getCourtVenues(baseLocation); - - if (!CollectionUtils.isEmpty(courtVenues)) { - CourtVenue courtVenue = courtVenues.getFirst(); - pcsCase.setHearingLocation(courtVenue.courtName()); - } else { - log.warn("Unable to find hearing location for case {}:", eventPayload.caseReference()); - pcsCase.setHearingLocation("Unable to find hearing location"); - } - } catch (Exception e) { - log.warn("Unable to fetch hearing location for case {}:", eventPayload.caseReference(), e); - pcsCase.setHearingLocation("Unable to find hearing location"); - } + setHearingLocation(eventPayload, pcsCase); List futureHearings = getFutureHearings(pcsCaseEntity); @@ -133,6 +120,33 @@ private PCSCase start(EventPayload eventPayload) { return pcsCase; } + private void setHearingLocation(EventPayload eventPayload, PCSCase pcsCase) { + CaseLocation caseManagementLocation = pcsCase.getCaseManagementLocation(); + + if (caseManagementLocation == null) { + log.warn("Unable to find hearing location for case {}:", eventPayload.caseReference()); + pcsCase.setHearingLocation("Unable to find hearing location"); + return; + } + + List baseLocation = List.of(Integer.parseInt(caseManagementLocation.getBaseLocation())); + + try { + List courtVenues = locationReferenceService.getCourtVenues(baseLocation); + + if (!CollectionUtils.isEmpty(courtVenues)) { + CourtVenue courtVenue = courtVenues.getFirst(); + pcsCase.setHearingLocation(courtVenue.courtName()); + } else { + log.warn("Unable to find hearing location for case {}:", eventPayload.caseReference()); + pcsCase.setHearingLocation("Unable to find hearing location"); + } + } catch (Exception e) { + log.warn("Unable to fetch hearing location for case {}:", eventPayload.caseReference(), e); + pcsCase.setHearingLocation("Unable to find hearing location"); + } + } + private SubmitResponse submit(EventPayload eventPayload) { long caseReference = eventPayload.caseReference(); PCSCase caseData = eventPayload.caseData(); diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java index dd41c1cc24..cb3a5323c2 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java @@ -113,7 +113,7 @@ void shouldConfigurePages() { @DisplayName("Start Handler") class StartHandlerTests { - @Mock + @Mock(strictness = LENIENT) private CaseLocation caseLocation; @BeforeEach @@ -258,6 +258,19 @@ void shouldSetHearingSummaryMarkdown() { assertThat(response.getHearing().getHearingSummaryMarkdown()).isEqualTo(expectedHearingSummaryMarkdown); } + @Test + void shouldHandleNoCaseManagementLocation() { + // Given + PCSCase pcsCase = PCSCase.builder() + .build(); + + // When + PCSCase response = callStartHandler(pcsCase); + + // Then + assertThat(response.getHearingLocation()).isEqualTo("Unable to find hearing location"); + } + @Test void shouldHandleExceptionGettingHearingVenue() { // Given From 1fb24ed84c82df7b0f60ffec121393648493c7f0 Mon Sep 17 00:00:00 2001 From: sstewart <202802468+scottstewart-sl@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:58:33 +0100 Subject: [PATCH 5/6] HDPI-6517: Fix next hearing ordering --- .../pcs/ccd/event/hearing/ManageHearing.java | 2 +- .../pcs/ccd/event/ManageHearingTest.java | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java index 82a64d884e..b7d57bbc82 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/hearing/ManageHearing.java @@ -213,7 +213,7 @@ private List getFutureHearings(PcsCaseEntity pcsCaseEntity) { return pcsCaseEntity.getHearings().stream() .filter(hearingEntity -> hearingEntity.getHearingDate().isAfter(now)) .filter(hearingEntity -> BooleanUtils.isNotTrue(hearingEntity.getCancelled())) - .sorted(Comparator.comparing(HearingEntity::getHearingDate).reversed()) + .sorted(Comparator.comparing(HearingEntity::getHearingDate)) .toList(); } diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java index cb3a5323c2..748c5d295a 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/ManageHearingTest.java @@ -231,6 +231,36 @@ void shouldSetHearingLocation() { assertThat(response.getHearingLocation()).isEqualTo(expectedHearingLocation); } + @Test + void shouldSetNextHearingId() { + // Given + HearingEntity hearingEntity1 = createHearing(FIXED_TEST_TIME.minusSeconds(1)); + hearingEntity1.setId(1001L); + + HearingEntity hearingEntity2 = createHearing(FIXED_TEST_TIME.plusSeconds(1)); + hearingEntity2.setId(1002L); + + HearingEntity hearingEntity3 = createHearing(FIXED_TEST_TIME.plusSeconds(2)); + hearingEntity3.setId(1003L); + + HearingEntity hearingEntity4 = createHearing(FIXED_TEST_TIME.plusSeconds(3)); + hearingEntity4.setId(1004L); + + when(pcsCaseEntity.getHearings()) + .thenReturn(List.of(hearingEntity1, hearingEntity2, hearingEntity3, hearingEntity4)); + + PCSCase pcsCase = PCSCase.builder() + .caseManagementLocation(caseLocation) + .hearing(new Hearing()) + .build(); + + // When + PCSCase response = callStartHandler(pcsCase); + + // Then + assertThat(response.getHearing().getHearingId()).isEqualTo(1002L); + } + @Test void shouldSetHearingSummaryMarkdown() { // Given From 8e4925fa8a143200f03396adce7ffb4e2f0c00bf Mon Sep 17 00:00:00 2001 From: sstewart <202802468+scottstewart-sl@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:19:42 +0100 Subject: [PATCH 6/6] HDPI-6637: Update DB migration version following merge from master --- ...ng_cancelled_flag.sql => V003__add_hearing_cancelled_flag.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V136__add_hearing_cancelled_flag.sql => V003__add_hearing_cancelled_flag.sql} (100%) diff --git a/src/main/resources/db/migration/V136__add_hearing_cancelled_flag.sql b/src/main/resources/db/migration/V003__add_hearing_cancelled_flag.sql similarity index 100% rename from src/main/resources/db/migration/V136__add_hearing_cancelled_flag.sql rename to src/main/resources/db/migration/V003__add_hearing_cancelled_flag.sql