Skip to content

Added logic for creation, mangemnet and display of case links#1527

Open
aschalew-at wants to merge 30 commits intomasterfrom
HDPI-4510-create-and-view-proposed-linked-cases
Open

Added logic for creation, mangemnet and display of case links#1527
aschalew-at wants to merge 30 commits intomasterfrom
HDPI-4510-create-and-view-proposed-linked-cases

Conversation

@aschalew-at
Copy link
Copy Markdown

Jira link

See [HDPI-4510](https://tools.hmcts.net/jira/browse/HDPI-4510)
[HDPI-4509](https://tools.hmcts.net/jira/browse/HDPI-4509)
[HDPI-4512](https://tools.hmcts.net/jira/browse/HDPI-4512)

Change description

This is about case linking which is linking of cases based on certain reasons.The PR includes changes for creation, managing and displaying of cases links.

Testing done

Security Vulnerability Assessment

CVE Suppression: Are there any CVEs present in the codebase (either newly introduced or pre-existing) that are being intentionally suppressed or ignored by this commit?

  • Yes
  • No

Checklist

  • commit messages are meaningful and follow good commit message guidelines
  • README and other documentation has been updated / added (if needed)
  • tests have been updated / new tests has been added (if needed)
  • Does this PR introduce a breaking change

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 18, 2026

CCD diff summary

👉 Full report: https://github.com/hmcts/pcs-api/actions/runs/23911120884?check_suite_focus=true

AuthorisationCaseState.json

UserRoleCaseStateIDCRUD
+caseworker-pcs
+AWAITING_SUBMISSION_TO_HMCTS
+R
+caseworker-pcs
+PENDING_CASE_ISSUED
+R

CaseField.json

RegularExpressionMaxSearchableMinFieldTypeParameterHintTextLabelIDFieldType
+
+
+
+
+
+
+Component Launcher (for displaying Linked Cases data)
+LinkedCasesComponentLauncher
+ComponentLauncher
+
+
+
+
+CaseLink
+
+Linked cases
+caseLinks
+Collection

AuthorisationCaseEvent/AuthorisationCaseEvent.json

UserRoleCaseEventIDCRUD
+caseworker-pcs
+createCaseLink
+R
+caseworker-pcs-solicitor
+createCaseLink
+CRU
+caseworker-pcs
+maintainCaseLink
+R
+caseworker-pcs-solicitor
+maintainCaseLink
+CRUD

AuthorisationCaseField/caseworker-pcs.json

UserRoleCaseFieldIDCRUD
+caseworker-pcs
+LinkedCasesComponentLauncher
+R
+caseworker-pcs
+caseHistory
+CRU
+caseworker-pcs
+caseLinks
+R
+caseworker-pcs
+caseTitleMarkdown
+R
+caseworker-pcs
+nextStepsMarkdown
+R
+caseworker-pcs
+nextStepsMarkdownLabel
+R
+caseworker-pcs
+propertyAddress
+R
+caseworker-pcs
+waysToPay
+R

AuthorisationCaseField/caseworker-pcs-solicitor.json

UserRoleCaseFieldIDCRUD
+caseworker-pcs-solicitor
+LinkedCasesComponentLauncher
+CRUD
+caseworker-pcs-solicitor
+caseLinks
+CRUD

CaseEvent/maintainCaseLink.json

DescriptionEndButtonLabelIDNamePostConditionStatePreConditionState(s)PublishShowEventNotesShowSummary
+To manage link related cases
+Save and continue
+maintainCaseLink
+Manage case links
+*
+*
+N
+N
+N

CaseEvent/createCaseLink.json

DescriptionEndButtonLabelIDNamePostConditionStatePreConditionState(s)PublishShowEventNotesShowSummary
+To link related cases
+Save and continue
+createCaseLink
+Link cases
+*
+*
+N
+N
+N

CaseEventToFields/maintainCaseLink.json

CaseEventIDCaseFieldIDDisplayContextDisplayContextParameterPageColumnNumberPageDisplayOrderPageFieldDisplayOrderPageIDPageLabelShowSummaryChangeOption
+maintainCaseLink
+LinkedCasesComponentLauncher
+OPTIONAL
+#ARGUMENT(UPDATE,LinkedCases)
+1
+1
+2
+maintainCaseLink
+Case Link
+Y
+maintainCaseLink
+caseLinks
+OPTIONAL
+LinkedCasesComponentLauncher = "DONOTSHOW"
+1
+1
+1
+maintainCaseLink
+Case Link
+Y
+Y

CaseEventToFields/createCaseLink.json

CaseEventIDCaseFieldIDDisplayContextDisplayContextParameterPageColumnNumberPageDisplayOrderPageFieldDisplayOrderPageIDPageLabelShowSummaryChangeOption
+createCaseLink
+LinkedCasesComponentLauncher
+OPTIONAL
+#ARGUMENT(CREATE,LinkedCases)
+1
+1
+2
+createCaseLink
+Case Link
+Y
+createCaseLink
+caseLinks
+OPTIONAL
+LinkedCasesComponentLauncher = "DONOTSHOW"
+1
+1
+1
+createCaseLink
+Case Link
+Y
+Y

CaseTypeTab/6_caseLinkscaseworker-pcs-solicitor.json

CaseFieldIDChannelDisplayContextParameterTabDisplayOrderTabFieldDisplayOrderTabIDTabLabelUserRole
+LinkedCasesComponentLauncher
+CaseWorker
+#ARGUMENT(LinkedCases)
+6
+1
+caseLinkscaseworker-pcs-solicitor
+Linked cases
+caseworker-pcs-solicitor
+caseLinks
+CaseWorker
+#ARGUMENT(LinkedCases)
+LinkedCasesComponentLauncher!=""
+6
+2
+caseLinkscaseworker-pcs-solicitor
+Linked cases
+

@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 18, 2026 12:22 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 18, 2026 14:10 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 18, 2026 16:31 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 19, 2026 10:00 Abandoned
@aschalew-at aschalew-at requested a review from a team as a code owner March 19, 2026 10:46
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 19, 2026 11:02 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 19, 2026 15:30 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 19, 2026 17:19 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 20, 2026 07:32 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 20, 2026 09:32 Abandoned
@hmcts-jenkins-j-to-z hmcts-jenkins-j-to-z bot requested a deployment to preview March 20, 2026 11:01 Abandoned
}

this.caseLinks.clear();
this.caseLinks.addAll(result);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for not just assigning result, (which could have a better name btw), to this.caseLinks ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. The caseLinks field in PcsCaseEntity is annotated with @onetomany with orphanRemoval set to true, Hibernate tracks the original collection instance and expects the same collection to be mutated and not swapped out. result renamed to mergedCaseLinkEntities.

@Override
public SetMultimap<HasRole, Permission> getGrants() {
SetMultimap<HasRole, Permission> grants = HashMultimap.create();
grants.putAll(PCS_SOLICITOR, Permission.CRU);
Copy link
Copy Markdown
Contributor

@guygrewal77 guygrewal77 Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the Delete missing for permissions ? In the event - CreateCaseLink class, solicitor has full CRUD access.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The access profile in CaseLinkinAccess is applied to the fields in PCSCase (for updating cases) do not need the Delete permission in association with the events CreateCaseLink and MaintainLinkCase. In the event class CreateCaseLink, we only need the permissions CRU, whereas in the event class MaintainLinkCase, we need the Delete permission as well. Hence I will remove the Delete permission in CreateCaseLink and keep it in MaintainLinkCase. I will keep CRU in CaseLinkinAccess class

Copy link
Copy Markdown
Contributor

@tvr-hmcts tvr-hmcts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments for consideration

.build();
caseLinkReasonEntities.add(caseLinkReasonEntity);
}
caseLinkEntity.getReasons().clear();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this call needed again here?

}

@Test
void shouldAddCaseLinkReason() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just testing a setter no? Guess I am not understanding the value.


if (dto.getReasonForLink() != null) {
List<CaseLinkReasonEntity> caseLinkReasonEntities = new ArrayList<>();
for (ListValue<LinkReason> incomingLinkReason : dto.getReasonForLink()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be streamed into the reasons collection within the entity rather than create this new arraylist.

@Component
public class CaseLinkView {

public void setCaseFields(PCSCase pcsCase, PcsCaseEntity pcsCaseEntity) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not understanding the point in this wrapper. The same check is made within the other method also. So the other can just be public


private final PcsCaseService pcsCaseService;


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra space

}

@Test
void shouldPatchCaseData() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need another test for when the pcsCase.getCaseLinks() != null

// Given
PcsCaseEntity pcsCaseEntity = mock(PcsCaseEntity.class);
PCSCase caseData = PCSCase.builder().build();
when(pcsCaseRepository.findByCaseReference(CASE_REFERENCE)).thenReturn(java.util.Optional.of(pcsCaseEntity));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import java.util.Optional

underTest.patchCaseLinks(CASE_REFERENCE, caseData);

// Then
verify(caseLinkService, atLeastOnce()).mergeCaseLinks(caseData.getCaseLinks(), pcsCaseEntity);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of times can be asserted more strictly here.

}

@Test
void shouldModifyCaseLinksInSubmitCallback() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in here is a duplication of the other test CreateCaseLinkTest

// Then
List<ListValue<CaseLink>> mappedCaseLinks = pcsCase.getCaseLinks();
assertThat(mappedCaseLinks).hasSize(1);
assertThat(mappedCaseLinks.getFirst().getValue().getCaseReference()).isEqualTo("1234");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mappedCaseLinks.getFirst().getValue() can be extracted and the variable used in the other asserts rather than call the same chain each time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants