From ff2713be1d3d03740aad6b996e570725d78fc63c Mon Sep 17 00:00:00 2001 From: fudge88 Date: Thu, 21 May 2026 14:04:21 +0100 Subject: [PATCH 01/13] added event seperate to additionalDocumenets --- .../hmcts/reform/pcs/ccd/domain/PCSCase.java | 2 + .../hmcts/reform/pcs/ccd/event/EventId.java | 3 +- .../ccd/event/citizen/UploadDocuments.java | 66 ++++++++++ .../ccd/service/document/DocumentService.java | 47 +++++++ .../event/citizen/UploadDocumentsTest.java | 107 ++++++++++++++++ .../service/document/DocumentServiceTest.java | 117 ++++++++++++++++++ 6 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java create mode 100644 src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java 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 7feb3cef0a..9d848b71a3 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 @@ -431,6 +431,8 @@ public class PCSCase { ) private List> additionalDocuments; + private List> uploadedAdditionalDocuments; + @CCD( label = "Are you planning to make an application at the same time as your claim?", hint = "After you’ve submitted your claim, there will be instructions on how to make an application" diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java index 87052e0c24..9be9d72da4 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java @@ -12,5 +12,6 @@ public enum EventId { createCaseLink, maintainCaseLink, dashboardView, - confirmEviction + confirmEviction, + uploadDocuments } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java new file mode 100644 index 0000000000..731f5aa2eb --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java @@ -0,0 +1,66 @@ +package uk.gov.hmcts.reform.pcs.ccd.event.citizen; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import uk.gov.hmcts.ccd.sdk.api.CCDConfig; +import uk.gov.hmcts.ccd.sdk.api.DecentralisedConfigBuilder; +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.reform.pcs.ccd.ShowConditions; +import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.UserRole; +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.entity.PcsCaseEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; +import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; +import uk.gov.hmcts.reform.pcs.ccd.service.document.DocumentService; +import uk.gov.hmcts.reform.pcs.ccd.service.party.PartyService; +import uk.gov.hmcts.reform.pcs.security.SecurityContextService; + +import java.util.UUID; + +import static uk.gov.hmcts.reform.pcs.ccd.event.EventId.uploadDocuments; + +@Slf4j +@Component +@AllArgsConstructor +public class UploadDocuments implements CCDConfig { + + private final PcsCaseService pcsCaseService; + private final PartyService partyService; + private final SecurityContextService securityContextService; + private final DocumentService documentService; + + @Override + public void configureDecentralised(DecentralisedConfigBuilder configBuilder) { + configBuilder + .decentralisedEvent(uploadDocuments.name(), this::submit) + .forStates(State.CASE_ISSUED) + .name("Upload additional documents") + .showCondition(ShowConditions.NEVER_SHOW) + .grant(Permission.CR, UserRole.DEFENDANT); + } + + private SubmitResponse submit(EventPayload eventPayload) { + long caseReference = eventPayload.caseReference(); + PCSCase caseData = eventPayload.caseData(); + + PcsCaseEntity pcsCaseEntity = pcsCaseService.loadCase(caseReference); + PartyEntity uploadingParty = getCurrentPartyEntity(caseReference); + + documentService.createAdditionalDocumentsForParty( + caseData.getUploadedAdditionalDocuments(), + pcsCaseEntity, + uploadingParty + ); + + return SubmitResponse.builder().build(); + } + + private PartyEntity getCurrentPartyEntity(long caseReference) { + UUID currentUserId = securityContextService.getCurrentUserId(); + return partyService.getPartyEntityByIdamId(currentUserId, caseReference); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java index 0cdf399950..6600e565ae 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java @@ -34,6 +34,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; @Slf4j @AllArgsConstructor @@ -180,6 +182,51 @@ private DocumentType mapAdditionalDocumentTypeToDocumentType(AdditionalDocumentT }; } + public List createAdditionalDocumentsForParty( + List> uploadedDocuments, + PcsCaseEntity pcsCase, + PartyEntity party + ) { + if (CollectionUtils.isEmpty(uploadedDocuments)) { + log.info("No additional documents to save for case {}", pcsCase.getCaseReference()); + return Collections.emptyList(); + } + + Set existingUrls = pcsCase.getDocuments().stream() + .map(DocumentEntity::getUrl) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + List documentEntities = uploadedDocuments.stream() + .map(ListValue::getValue) + .filter(Objects::nonNull) + .filter(uploaded -> uploaded.getDocument() != null) + .filter(uploaded -> !existingUrls.contains(uploaded.getDocument().getUrl())) + .map(uploaded -> DocumentEntity.builder() + .pcsCase(pcsCase) + .party(party) + .url(uploaded.getDocument().getUrl()) + .fileName(uploaded.getDocument().getFilename()) + .displayFileName(uploaded.getDocument().getFilename()) + .binaryUrl(uploaded.getDocument().getBinaryUrl()) + .contentType(uploaded.getContentType()) + .size(uploaded.getSizeInBytes()) + .type(DocumentType.OTHER) + .build()) + .toList(); + + if (documentEntities.isEmpty()) { + log.info("All additional documents for case {} already persisted; nothing to save", + pcsCase.getCaseReference()); + return Collections.emptyList(); + } + + List saved = documentRepository.saveAll(documentEntities); + log.info("Saved {} additional documents for case {} and party {}", + saved.size(), pcsCase.getCaseReference(), party.getId()); + return saved; + } + public List createDefendantUploadedDocuments( List> defendantDocuments, DefendantResponseEntity defendantResponse, diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java new file mode 100644 index 0000000000..cbd1fba1cb --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java @@ -0,0 +1,107 @@ +package uk.gov.hmcts.reform.pcs.ccd.event.citizen; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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.Document; +import uk.gov.hmcts.ccd.sdk.type.ListValue; +import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; +import uk.gov.hmcts.reform.pcs.ccd.domain.UploadedDocument; +import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; +import uk.gov.hmcts.reform.pcs.ccd.event.BaseEventTest; +import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; +import uk.gov.hmcts.reform.pcs.ccd.service.document.DocumentService; +import uk.gov.hmcts.reform.pcs.ccd.service.party.PartyService; +import uk.gov.hmcts.reform.pcs.security.SecurityContextService; + +import java.util.List; +import java.util.UUID; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class UploadDocumentsTest extends BaseEventTest { + + @Mock + private PcsCaseService pcsCaseService; + @Mock + private PartyService partyService; + @Mock + private SecurityContextService securityContextService; + @Mock + private DocumentService documentService; + + @BeforeEach + void setUp() { + UploadDocuments underTest = new UploadDocuments(pcsCaseService, partyService, + securityContextService, documentService); + setEventUnderTest(underTest); + } + + @Nested + @DisplayName("Submit event tests") + class SubmitTests { + + @Mock + private PcsCaseEntity pcsCaseEntity; + + @BeforeEach + void setUp() { + given(pcsCaseService.loadCase(TEST_CASE_REFERENCE)).willReturn(pcsCaseEntity); + } + + @Test + void shouldPersistUploadedDocumentsForCurrentParty() { + // Given + UploadedDocument uploaded = UploadedDocument.builder() + .document(Document.builder() + .url("url-1").filename("file-1.pdf").binaryUrl("bin-1").build()) + .build(); + + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(uploaded).build() + ); + + PCSCase caseData = PCSCase.builder() + .uploadedAdditionalDocuments(uploadedDocs) + .build(); + + PartyEntity currentParty = stubCurrentUserParty(); + + // When + callSubmitHandler(caseData); + + // Then + verify(documentService).createAdditionalDocumentsForParty(uploadedDocs, pcsCaseEntity, currentParty); + } + + @Test + void shouldDelegateEvenWhenNoDocumentsSent() { + // Given + PCSCase caseData = PCSCase.builder().build(); + PartyEntity currentParty = stubCurrentUserParty(); + + // When + callSubmitHandler(caseData); + + // Then — service handles null/empty; event passes through + verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty); + } + + private PartyEntity stubCurrentUserParty() { + PartyEntity currentUserParty = mock(PartyEntity.class); + UUID currentUserId = UUID.randomUUID(); + given(securityContextService.getCurrentUserId()).willReturn(currentUserId); + given(partyService.getPartyEntityByIdamId(currentUserId, TEST_CASE_REFERENCE)) + .willReturn(currentUserParty); + return currentUserParty; + } + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java index e653f04ca6..da94647952 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java @@ -719,6 +719,123 @@ void shouldSaveDefendantEvidenceWithNullMetadata() { assertThat(entities.getFirst().getSize()).isNull(); } + @Test + void shouldSaveAdditionalDocumentsForPartyAsOtherType() { + // Given + PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); + PartyEntity party = mock(PartyEntity.class); + when(pcsCase.getDocuments()).thenReturn(new ArrayList<>()); + + UploadedDocument uploaded = UploadedDocument.builder() + .document(Document.builder() + .url("url-new").filename("file-new.pdf").binaryUrl("bin-new").build()) + .contentType("application/pdf") + .sizeInBytes(123L) + .build(); + + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(uploaded).build() + ); + + when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); + + // When + underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party); + + // Then + verify(documentRepository).saveAll(documentEntityListCaptor.capture()); + List entities = documentEntityListCaptor.getValue(); + assertThat(entities).hasSize(1); + DocumentEntity entity = entities.getFirst(); + assertThat(entity.getType()).isEqualTo(DocumentType.OTHER); + assertThat(entity.getCategoryId()).isNull(); + assertThat(entity.getUrl()).isEqualTo("url-new"); + assertThat(entity.getFileName()).isEqualTo("file-new.pdf"); + assertThat(entity.getDisplayFileName()).isEqualTo("file-new.pdf"); + assertThat(entity.getBinaryUrl()).isEqualTo("bin-new"); + assertThat(entity.getContentType()).isEqualTo("application/pdf"); + assertThat(entity.getSize()).isEqualTo(123L); + assertThat(entity.getPcsCase()).isSameAs(pcsCase); + assertThat(entity.getParty()).isSameAs(party); + } + + @Test + void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { + // Given + PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); + PartyEntity party = mock(PartyEntity.class); + + DocumentEntity existing = DocumentEntity.builder().url("url-existing").build(); + List existingDocs = new ArrayList<>(); + existingDocs.add(existing); + when(pcsCase.getDocuments()).thenReturn(existingDocs); + + UploadedDocument duplicate = UploadedDocument.builder() + .document(Document.builder() + .url("url-existing").filename("dup.pdf").binaryUrl("bin-dup").build()) + .build(); + UploadedDocument fresh = UploadedDocument.builder() + .document(Document.builder() + .url("url-new").filename("new.pdf").binaryUrl("bin-new").build()) + .build(); + + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(duplicate).build(), + ListValue.builder().id("2").value(fresh).build() + ); + + when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); + + // When + underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party); + + // Then + verify(documentRepository).saveAll(documentEntityListCaptor.capture()); + List entities = documentEntityListCaptor.getValue(); + assertThat(entities).hasSize(1); + assertThat(entities.getFirst().getUrl()).isEqualTo("url-new"); + } + + @Test + void shouldNotCallRepositoryWhenAllAdditionalDocumentsAreDuplicates() { + // Given + PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); + PartyEntity party = mock(PartyEntity.class); + + List existingDocs = new ArrayList<>(); + existingDocs.add(DocumentEntity.builder().url("url-existing").build()); + when(pcsCase.getDocuments()).thenReturn(existingDocs); + + UploadedDocument duplicate = UploadedDocument.builder() + .document(Document.builder() + .url("url-existing").filename("dup.pdf").binaryUrl("bin-dup").build()) + .build(); + + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(duplicate).build() + ); + + // When + underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party); + + // Then + verify(documentRepository, never()).saveAll(anyList()); + } + + @Test + void shouldReturnEmptyListWhenAdditionalDocumentsInputIsNullOrEmpty() { + // Given + PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); + PartyEntity party = mock(PartyEntity.class); + + // When + underTest.createAdditionalDocumentsForParty(null, pcsCase, party); + underTest.createAdditionalDocumentsForParty(Collections.emptyList(), pcsCase, party); + + // Then + verify(documentRepository, never()).saveAll(anyList()); + } + private static Stream additionalDocumentCategoryScenarios() { return Stream.of( Arguments.of( From bdb562abfdb12b81ce1d9a2653020df928749061 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Thu, 21 May 2026 15:17:32 +0100 Subject: [PATCH 02/13] fixed test --- .../reform/pcs/ccd/service/document/DocumentServiceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java index da94647952..b988a6f25c 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java @@ -763,7 +763,6 @@ void shouldSaveAdditionalDocumentsForPartyAsOtherType() { void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { // Given PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); - PartyEntity party = mock(PartyEntity.class); DocumentEntity existing = DocumentEntity.builder().url("url-existing").build(); List existingDocs = new ArrayList<>(); @@ -786,6 +785,8 @@ void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); + PartyEntity party = mock(PartyEntity.class); + // When underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party); From 4a0f4a05a30dab020f373dcfcb87c497838fc9a0 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Fri, 22 May 2026 09:42:43 +0100 Subject: [PATCH 03/13] ccd label --- src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java | 1 + 1 file changed, 1 insertion(+) 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 d493309842..df04e7a705 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 @@ -446,6 +446,7 @@ public class PCSCase { ) private List> additionalDocuments; + @CCD(label = "Uploaded additional documents") private List> uploadedAdditionalDocuments; @CCD( From e1ef3afc74e087b1bb5423766a1c64f419130826 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Fri, 22 May 2026 10:49:52 +0100 Subject: [PATCH 04/13] annotation fix --- .../java/uk/gov/hmcts/reform/pcs/ccd/domain/PCSCase.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 df04e7a705..00c1162e0b 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 @@ -446,7 +446,10 @@ public class PCSCase { ) private List> additionalDocuments; - @CCD(label = "Uploaded additional documents") + @CCD( + access = DefendantAccess.class, + searchable = false + ) private List> uploadedAdditionalDocuments; @CCD( From 6a9df4bc8c53a7f309c216e2244004248e51c124 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Tue, 26 May 2026 09:18:48 +0100 Subject: [PATCH 05/13] merged master - gen apps --- .../hmcts/reform/pcs/ccd/domain/PCSCase.java | 4 + .../DocumentUploadCategory.java | 16 ++ .../documentupload/DocumentUploadDetails.java | 34 ++++ .../RelatedApplicationOption.java | 21 ++ .../ccd/event/citizen/UploadDocuments.java | 98 ++++++++- .../event/citizen/UploadDocumentsTest.java | 187 +++++++++++++++++- 6 files changed, 352 insertions(+), 8 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadCategory.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java 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 a71d916963..5f692cc789 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 @@ -29,6 +29,7 @@ import uk.gov.hmcts.reform.pcs.ccd.domain.genapp.CitizenGenAppRequest; import uk.gov.hmcts.reform.pcs.ccd.domain.genapp.GeneralApplication; import uk.gov.hmcts.reform.pcs.ccd.domain.genapp.XuiGenAppRequest; +import uk.gov.hmcts.reform.pcs.ccd.domain.documentupload.DocumentUploadDetails; import uk.gov.hmcts.reform.pcs.ccd.domain.grounds.AssuredNoArrearsPossessionGrounds; import uk.gov.hmcts.reform.pcs.ccd.domain.grounds.AssuredRentArrearsPossessionGrounds; import uk.gov.hmcts.reform.pcs.ccd.domain.grounds.ClaimGroundSummary; @@ -453,6 +454,9 @@ public class PCSCase { ) private List> uploadedAdditionalDocuments; + @JsonUnwrapped + private DocumentUploadDetails documentUploadDetails; + @CCD( label = "Are you planning to make an application at the same time as your claim?", hint = "After you’ve submitted your claim, there will be instructions on how to make an application" diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadCategory.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadCategory.java new file mode 100644 index 0000000000..3b52b5e956 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadCategory.java @@ -0,0 +1,16 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain.documentupload; + +import uk.gov.hmcts.ccd.sdk.api.HasLabel; + +public enum DocumentUploadCategory implements HasLabel { + + ADJOURN_HEARING_APPLICATION, + SUSPEND_EVICTION_APPLICATION, + SET_ASIDE_ORDER_APPLICATION, + GENERAL_APPLICATION; + + @Override + public String getLabel() { + return name(); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java new file mode 100644 index 0000000000..e4cb8eb8d5 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java @@ -0,0 +1,34 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain.documentupload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import uk.gov.hmcts.ccd.sdk.api.CCD; +import uk.gov.hmcts.ccd.sdk.type.ListValue; +import uk.gov.hmcts.ccd.sdk.type.YesOrNo; +import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.DefendantAccess; + +import java.util.List; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class DocumentUploadDetails { + + @CCD( + access = {DefendantAccess.class}, + searchable = false + ) + private YesOrNo showRelatedApplicationsPage; + + @CCD( + access = {DefendantAccess.class}, + searchable = false + ) + private List> relatedApplicationOptions; +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java new file mode 100644 index 0000000000..61b17efcdf --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java @@ -0,0 +1,21 @@ +package uk.gov.hmcts.reform.pcs.ccd.domain.documentupload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(Include.NON_NULL) +public class RelatedApplicationOption { + + private DocumentUploadCategory category; + private LocalDateTime submittedDate; +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java index 731f5aa2eb..1ecd057adb 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java @@ -8,10 +8,18 @@ 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.ListValue; +import uk.gov.hmcts.ccd.sdk.type.YesOrNo; import uk.gov.hmcts.reform.pcs.ccd.ShowConditions; import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.UserRole; 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.documentupload.DocumentUploadCategory; +import uk.gov.hmcts.reform.pcs.ccd.domain.documentupload.DocumentUploadDetails; +import uk.gov.hmcts.reform.pcs.ccd.domain.documentupload.RelatedApplicationOption; +import uk.gov.hmcts.reform.pcs.ccd.domain.genapp.GenAppState; +import uk.gov.hmcts.reform.pcs.ccd.domain.genapp.GenAppType; +import uk.gov.hmcts.reform.pcs.ccd.entity.GenAppEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; @@ -19,6 +27,14 @@ import uk.gov.hmcts.reform.pcs.ccd.service.party.PartyService; import uk.gov.hmcts.reform.pcs.security.SecurityContextService; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.UUID; import static uk.gov.hmcts.reform.pcs.ccd.event.EventId.uploadDocuments; @@ -28,6 +44,9 @@ @AllArgsConstructor public class UploadDocuments implements CCDConfig { + private static final Set VISIBLE_GEN_APP_STATES = + EnumSet.of(GenAppState.PENDING_SUBMISSION, GenAppState.SUBMITTED); + private final PcsCaseService pcsCaseService; private final PartyService partyService; private final SecurityContextService securityContextService; @@ -36,11 +55,86 @@ public class UploadDocuments implements CCDConfig { @Override public void configureDecentralised(DecentralisedConfigBuilder configBuilder) { configBuilder - .decentralisedEvent(uploadDocuments.name(), this::submit) + .decentralisedEvent(uploadDocuments.name(), this::submit, this::start) .forStates(State.CASE_ISSUED) .name("Upload additional documents") .showCondition(ShowConditions.NEVER_SHOW) - .grant(Permission.CR, UserRole.DEFENDANT); + .grant(Permission.CRU, UserRole.DEFENDANT); + } + + private PCSCase start(EventPayload eventPayload) { + long caseReference = eventPayload.caseReference(); + PCSCase caseData = eventPayload.caseData(); + + if (caseData.getDocumentUploadDetails() == null) { + caseData.setDocumentUploadDetails(new DocumentUploadDetails()); + } + + PcsCaseEntity pcsCaseEntity = pcsCaseService.loadCase(caseReference); + + List> options = Arrays.stream(DocumentUploadCategory.values()) + .map(category -> buildOption(category, pcsCaseEntity)) + .filter(Objects::nonNull) + .sorted(Comparator.comparing( + (ListValue listValue) -> listValue.getValue().getSubmittedDate()) + .reversed()) + .toList(); + + caseData.getDocumentUploadDetails().setRelatedApplicationOptions(new ArrayList<>(options)); + + caseData.getDocumentUploadDetails().setShowRelatedApplicationsPage( + options.isEmpty() ? YesOrNo.NO : YesOrNo.YES); + + return caseData; + } + + private ListValue buildOption(DocumentUploadCategory category, + PcsCaseEntity pcsCaseEntity) { + LocalDateTime submittedDate = findLatestGenAppDateForCategory(pcsCaseEntity, category); + if (submittedDate == null) { + return null; + } + return wrap(RelatedApplicationOption.builder() + .category(category) + .submittedDate(submittedDate) + .build()); + } + + private ListValue wrap(RelatedApplicationOption option) { + return ListValue.builder() + .id(option.getCategory().name()) + .value(option) + .build(); + } + + private LocalDateTime findLatestGenAppDateForCategory(PcsCaseEntity pcsCaseEntity, + DocumentUploadCategory category) { + if (pcsCaseEntity == null || pcsCaseEntity.getGenApps() == null) { + return null; + } + + GenAppType mapped = mapCategoryToGenAppType(category); + if (mapped == null) { + return null; + } + + return pcsCaseEntity.getGenApps().stream() + .filter(genApp -> genApp.getType() == mapped) + .filter(genApp -> VISIBLE_GEN_APP_STATES.contains(genApp.getState())) + .map(GenAppEntity::getApplicationSubmittedDate) + .filter(Objects::nonNull) + .max(Comparator.naturalOrder()) + .orElse(null); + } + + private GenAppType mapCategoryToGenAppType(DocumentUploadCategory category) { + return switch (category) { + case ADJOURN_HEARING_APPLICATION -> GenAppType.ADJOURN; + // SUSPEND was removed from GenAppType + case SUSPEND_EVICTION_APPLICATION -> null; + case SET_ASIDE_ORDER_APPLICATION -> GenAppType.SET_ASIDE; + case GENERAL_APPLICATION -> GenAppType.SOMETHING_ELSE; + }; } private SubmitResponse submit(EventPayload eventPayload) { diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java index cbd1fba1cb..bfbad8fe0c 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java @@ -9,8 +9,14 @@ import org.mockito.junit.jupiter.MockitoExtension; import uk.gov.hmcts.ccd.sdk.type.Document; import uk.gov.hmcts.ccd.sdk.type.ListValue; +import uk.gov.hmcts.ccd.sdk.type.YesOrNo; import uk.gov.hmcts.reform.pcs.ccd.domain.PCSCase; import uk.gov.hmcts.reform.pcs.ccd.domain.UploadedDocument; +import uk.gov.hmcts.reform.pcs.ccd.domain.documentupload.DocumentUploadCategory; +import uk.gov.hmcts.reform.pcs.ccd.domain.documentupload.DocumentUploadDetails; +import uk.gov.hmcts.reform.pcs.ccd.domain.genapp.GenAppState; +import uk.gov.hmcts.reform.pcs.ccd.domain.genapp.GenAppType; +import uk.gov.hmcts.reform.pcs.ccd.entity.GenAppEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; import uk.gov.hmcts.reform.pcs.ccd.event.BaseEventTest; @@ -19,12 +25,18 @@ import uk.gov.hmcts.reform.pcs.ccd.service.party.PartyService; import uk.gov.hmcts.reform.pcs.security.SecurityContextService; +import java.time.LocalDateTime; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class UploadDocumentsTest extends BaseEventTest { @@ -59,7 +71,6 @@ void setUp() { @Test void shouldPersistUploadedDocumentsForCurrentParty() { - // Given UploadedDocument uploaded = UploadedDocument.builder() .document(Document.builder() .url("url-1").filename("file-1.pdf").binaryUrl("bin-1").build()) @@ -75,23 +86,18 @@ void shouldPersistUploadedDocumentsForCurrentParty() { PartyEntity currentParty = stubCurrentUserParty(); - // When callSubmitHandler(caseData); - // Then verify(documentService).createAdditionalDocumentsForParty(uploadedDocs, pcsCaseEntity, currentParty); } @Test void shouldDelegateEvenWhenNoDocumentsSent() { - // Given PCSCase caseData = PCSCase.builder().build(); PartyEntity currentParty = stubCurrentUserParty(); - // When callSubmitHandler(caseData); - // Then — service handles null/empty; event passes through verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty); } @@ -104,4 +110,173 @@ private PartyEntity stubCurrentUserParty() { return currentUserParty; } } + + @Nested + @DisplayName("Start event tests") + class StartTests { + + @Mock + private PcsCaseEntity pcsCaseEntity; + + @BeforeEach + void setUp() { + given(pcsCaseService.loadCase(TEST_CASE_REFERENCE)).willReturn(pcsCaseEntity); + } + + @Test + void shouldReturnEmptyOptionsWhenNoGenAppsExist() { + when(pcsCaseEntity.getGenApps()).thenReturn(new HashSet<>()); + PCSCase caseData = PCSCase.builder().build(); + + PCSCase result = callStartHandler(caseData); + + DocumentUploadDetails details = result.getDocumentUploadDetails(); + assertThat(details).isNotNull(); + assertThat(details.getRelatedApplicationOptions()).isEmpty(); + assertThat(details.getShowRelatedApplicationsPage()).isEqualTo(YesOrNo.NO); + } + + @Test + void shouldIncludeAdjournCategoryWhenAdjournGenAppExists() { + LocalDateTime submittedDate = LocalDateTime.now(); + GenAppEntity adjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, submittedDate); + + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(adjourn)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + + DocumentUploadDetails details = result.getDocumentUploadDetails(); + assertThat(details.getRelatedApplicationOptions()) + .extracting(option -> option.getValue().getCategory()) + .containsExactly(DocumentUploadCategory.ADJOURN_HEARING_APPLICATION); + assertThat(details.getRelatedApplicationOptions().get(0).getValue().getSubmittedDate()) + .isEqualTo(submittedDate); + assertThat(details.getShowRelatedApplicationsPage()).isEqualTo(YesOrNo.YES); + } + + @Test + void shouldIncludeGenAppsInPendingSubmissionState() { + LocalDateTime submittedDate = LocalDateTime.now(); + GenAppEntity pending = stubGenApp(GenAppType.SET_ASIDE, GenAppState.PENDING_SUBMISSION, submittedDate); + + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(pending)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()) + .extracting(option -> option.getValue().getCategory()) + .containsExactly(DocumentUploadCategory.SET_ASIDE_ORDER_APPLICATION); + } + + @Test + void shouldExcludeGenAppsWithNoState() { + // Defensive: a genApp with a null state must not surface a radio option. + GenAppEntity stateless = stubGenApp(GenAppType.ADJOURN, null, LocalDateTime.now()); + + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(stateless)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()).isEmpty(); + assertThat(result.getDocumentUploadDetails().getShowRelatedApplicationsPage()) + .isEqualTo(YesOrNo.NO); + } + + @Test + void shouldSortOptionsByLatestSubmittedDateDescending() { + LocalDateTime now = LocalDateTime.now(); + GenAppEntity oldestAdjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, now.minusDays(10)); + GenAppEntity midSetAside = stubGenApp(GenAppType.SET_ASIDE, GenAppState.SUBMITTED, now.minusDays(3)); + GenAppEntity newestGeneral = stubGenApp(GenAppType.SOMETHING_ELSE, GenAppState.SUBMITTED, now); + + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(oldestAdjourn, midSetAside, newestGeneral)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()) + .extracting(option -> option.getValue().getCategory()) + .containsExactly( + DocumentUploadCategory.GENERAL_APPLICATION, + DocumentUploadCategory.SET_ASIDE_ORDER_APPLICATION, + DocumentUploadCategory.ADJOURN_HEARING_APPLICATION); + } + + @Test + void shouldUseLatestSubmittedDatePerCategoryWhenMultipleGenAppsShareAType() { + LocalDateTime older = LocalDateTime.now().minusDays(5); + LocalDateTime newer = LocalDateTime.now(); + GenAppEntity olderAdjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, older); + GenAppEntity newerAdjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, newer); + + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(olderAdjourn, newerAdjourn)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()) + .hasSize(1) + .first() + .satisfies(option -> { + assertThat(option.getValue().getCategory()) + .isEqualTo(DocumentUploadCategory.ADJOURN_HEARING_APPLICATION); + assertThat(option.getValue().getSubmittedDate()).isEqualTo(newer); + }); + } + + @Test + void shouldNotSurfaceSuspendCategoryWhileGenAppTypeSuspendIsAbsent() { + // SUSPEND was removed from GenAppType by PR #1804. Until it is restored, the + // SUSPEND_EVICTION_APPLICATION category must be filtered out so we don't render + // a radio backed by no data. + when(pcsCaseEntity.getGenApps()).thenReturn(new HashSet<>()); + PCSCase caseData = PCSCase.builder().build(); + + PCSCase result = callStartHandler(caseData); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()) + .extracting(option -> option.getValue().getCategory()) + .doesNotContain(DocumentUploadCategory.SUSPEND_EVICTION_APPLICATION); + } + + private GenAppEntity stubGenApp(GenAppType type, GenAppState state, LocalDateTime submittedDate) { + GenAppEntity entity = mock(GenAppEntity.class); + lenient().when(entity.getType()).thenReturn(type); + lenient().when(entity.getState()).thenReturn(state); + lenient().when(entity.getApplicationSubmittedDate()).thenReturn(submittedDate); + return entity; + } + + private Set setOf(GenAppEntity... entities) { + Set set = new HashSet<>(); + for (GenAppEntity entity : entities) { + set.add(entity); + } + return set; + } + + @Test + void shouldHandleNullGenAppsCollection() { + when(pcsCaseEntity.getGenApps()).thenReturn(null); + PCSCase caseData = PCSCase.builder().build(); + + PCSCase result = callStartHandler(caseData); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()).isEmpty(); + assertThat(result.getDocumentUploadDetails().getShowRelatedApplicationsPage()) + .isEqualTo(YesOrNo.NO); + } + + @Test + void shouldReuseExistingDetailsObjectIfAlreadySet() { + when(pcsCaseEntity.getGenApps()).thenReturn(new HashSet<>()); + + DocumentUploadDetails existing = new DocumentUploadDetails(); + PCSCase caseData = PCSCase.builder() + .documentUploadDetails(existing) + .build(); + + PCSCase result = callStartHandler(caseData); + + assertThat(result.getDocumentUploadDetails()).isSameAs(existing); + } + } } From df1917ddea3ecfacd36c6052737cffd2c847681c Mon Sep 17 00:00:00 2001 From: fudge88 Date: Tue, 26 May 2026 12:07:09 +0100 Subject: [PATCH 06/13] docmuemnt name service from 1839, refactored start call back, genappid collected and posted --- .../documentupload/DocumentUploadDetails.java | 7 ++ .../RelatedApplicationOption.java | 2 + .../ccd/event/citizen/UploadDocuments.java | 83 ++++++------- .../service/document/DocumentNameService.java | 81 +++++++++++++ .../ccd/service/document/DocumentService.java | 45 +++++-- .../event/citizen/UploadDocumentsTest.java | 92 +++++++++++++-- .../document/DocumentNameServiceTest.java | 110 ++++++++++++++++++ .../service/document/DocumentServiceTest.java | 76 ++++++++++-- 8 files changed, 427 insertions(+), 69 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameService.java create mode 100644 src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java index e4cb8eb8d5..e58423af58 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java @@ -12,6 +12,7 @@ import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.DefendantAccess; import java.util.List; +import java.util.UUID; @Builder @Data @@ -31,4 +32,10 @@ public class DocumentUploadDetails { searchable = false ) private List> relatedApplicationOptions; + + @CCD( + access = {DefendantAccess.class}, + searchable = false + ) + private UUID selectedRelatedApplicationId; } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java index 61b17efcdf..2c45db52f2 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java @@ -8,6 +8,7 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.UUID; @Builder @Data @@ -16,6 +17,7 @@ @JsonInclude(Include.NON_NULL) public class RelatedApplicationOption { + private UUID genAppId; private DocumentUploadCategory category; private LocalDateTime submittedDate; } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java index 1ecd057adb..96cea64e20 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java @@ -27,15 +27,14 @@ import uk.gov.hmcts.reform.pcs.ccd.service.party.PartyService; import uk.gov.hmcts.reform.pcs.security.SecurityContextService; -import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.stream.Stream; import static uk.gov.hmcts.reform.pcs.ccd.event.EventId.uploadDocuments; @@ -72,8 +71,8 @@ private PCSCase start(EventPayload eventPayload) { PcsCaseEntity pcsCaseEntity = pcsCaseService.loadCase(caseReference); - List> options = Arrays.stream(DocumentUploadCategory.values()) - .map(category -> buildOption(category, pcsCaseEntity)) + List> options = visibleGenApps(pcsCaseEntity) + .map(this::toOption) .filter(Objects::nonNull) .sorted(Comparator.comparing( (ListValue listValue) -> listValue.getValue().getSubmittedDate()) @@ -88,52 +87,40 @@ private PCSCase start(EventPayload eventPayload) { return caseData; } - private ListValue buildOption(DocumentUploadCategory category, - PcsCaseEntity pcsCaseEntity) { - LocalDateTime submittedDate = findLatestGenAppDateForCategory(pcsCaseEntity, category); - if (submittedDate == null) { - return null; + private Stream visibleGenApps(PcsCaseEntity pcsCaseEntity) { + if (pcsCaseEntity == null || pcsCaseEntity.getGenApps() == null) { + return Stream.empty(); } - return wrap(RelatedApplicationOption.builder() - .category(category) - .submittedDate(submittedDate) - .build()); + return pcsCaseEntity.getGenApps().stream() + .filter(Objects::nonNull) + .filter(genApp -> VISIBLE_GEN_APP_STATES.contains(genApp.getState())) + .filter(genApp -> genApp.getApplicationSubmittedDate() != null); } - private ListValue wrap(RelatedApplicationOption option) { + private ListValue toOption(GenAppEntity genApp) { + DocumentUploadCategory category = mapGenAppTypeToCategory(genApp.getType()); + if (category == null) { + return null; + } + RelatedApplicationOption option = RelatedApplicationOption.builder() + .genAppId(genApp.getId()) + .category(category) + .submittedDate(genApp.getApplicationSubmittedDate()) + .build(); return ListValue.builder() - .id(option.getCategory().name()) + .id(genApp.getId().toString()) .value(option) .build(); } - private LocalDateTime findLatestGenAppDateForCategory(PcsCaseEntity pcsCaseEntity, - DocumentUploadCategory category) { - if (pcsCaseEntity == null || pcsCaseEntity.getGenApps() == null) { - return null; - } - - GenAppType mapped = mapCategoryToGenAppType(category); - if (mapped == null) { + private DocumentUploadCategory mapGenAppTypeToCategory(GenAppType type) { + if (type == null) { return null; } - - return pcsCaseEntity.getGenApps().stream() - .filter(genApp -> genApp.getType() == mapped) - .filter(genApp -> VISIBLE_GEN_APP_STATES.contains(genApp.getState())) - .map(GenAppEntity::getApplicationSubmittedDate) - .filter(Objects::nonNull) - .max(Comparator.naturalOrder()) - .orElse(null); - } - - private GenAppType mapCategoryToGenAppType(DocumentUploadCategory category) { - return switch (category) { - case ADJOURN_HEARING_APPLICATION -> GenAppType.ADJOURN; - // SUSPEND was removed from GenAppType - case SUSPEND_EVICTION_APPLICATION -> null; - case SET_ASIDE_ORDER_APPLICATION -> GenAppType.SET_ASIDE; - case GENERAL_APPLICATION -> GenAppType.SOMETHING_ELSE; + return switch (type) { + case ADJOURN -> DocumentUploadCategory.ADJOURN_HEARING_APPLICATION; + case SET_ASIDE -> DocumentUploadCategory.SET_ASIDE_ORDER_APPLICATION; + case SOMETHING_ELSE -> DocumentUploadCategory.GENERAL_APPLICATION; }; } @@ -143,16 +130,30 @@ private SubmitResponse submit(EventPayload eventPayload) PcsCaseEntity pcsCaseEntity = pcsCaseService.loadCase(caseReference); PartyEntity uploadingParty = getCurrentPartyEntity(caseReference); + GenAppEntity selectedGenApp = resolveSelectedGenApp(caseData, pcsCaseEntity); documentService.createAdditionalDocumentsForParty( caseData.getUploadedAdditionalDocuments(), pcsCaseEntity, - uploadingParty + uploadingParty, + selectedGenApp ); return SubmitResponse.builder().build(); } + private GenAppEntity resolveSelectedGenApp(PCSCase caseData, PcsCaseEntity pcsCaseEntity) { + DocumentUploadDetails details = caseData.getDocumentUploadDetails(); + if (details == null || details.getSelectedRelatedApplicationId() == null) { + return null; + } + UUID selectedId = details.getSelectedRelatedApplicationId(); + return visibleGenApps(pcsCaseEntity) + .filter(genApp -> selectedId.equals(genApp.getId())) + .findFirst() + .orElse(null); + } + private PartyEntity getCurrentPartyEntity(long caseReference) { UUID currentUserId = securityContextService.getCurrentUserId(); return partyService.getPartyEntityByIdamId(currentUserId, caseReference); diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameService.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameService.java new file mode 100644 index 0000000000..ea110124f7 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameService.java @@ -0,0 +1,81 @@ +package uk.gov.hmcts.reform.pcs.ccd.service.document; + +import org.apache.commons.io.FilenameUtils; +import org.springframework.stereotype.Service; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.GenAppEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.ClaimPartyEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyRole; +import uk.gov.hmcts.reform.pcs.exception.PartyNotFoundException; + +import java.util.UUID; + +@Service +public class DocumentNameService { + + public String appendGenAppPostfix(String originalFilename, + GenAppEntity genAppEntity, + ClaimEntity mainClaim, + UUID applicantPartyId) { + + if (originalFilename == null) { + return null; + } + + String baseName = FilenameUtils.getBaseName(originalFilename); + String extension = FilenameUtils.getExtension(originalFilename); + + String partyLabel = getPartyLabel(mainClaim, applicantPartyId); + String filename = "%s GA%d".formatted(baseName, genAppEntity.getRank()); + + if (partyLabel != null) { + filename += " - " + partyLabel; + } + + if (!extension.isBlank()) { + filename += "." + extension; + } + + return filename; + } + + public String appendPartyPostfix(String originalFilename, + ClaimEntity mainClaim, + UUID applicantPartyId) { + + if (originalFilename == null) { + return null; + } + + String baseName = FilenameUtils.getBaseName(originalFilename); + String extension = FilenameUtils.getExtension(originalFilename); + + String partyLabel = getPartyLabel(mainClaim, applicantPartyId); + String filename = (partyLabel != null) ? "%s - %s".formatted(baseName, partyLabel) : baseName; + + if (!extension.isBlank()) { + filename += "." + extension; + } + + return filename; + } + + private static String getPartyLabel(ClaimEntity mainClaim, UUID partyId) { + ClaimPartyEntity applicantClaimParty = getClaimParty(mainClaim, partyId); + + if (applicantClaimParty.getRole() == PartyRole.CLAIMANT) { + return "Claimant %d".formatted(applicantClaimParty.getRank()); + } else if (applicantClaimParty.getRole() == PartyRole.DEFENDANT) { + return "Defendant %d".formatted(applicantClaimParty.getRank()); + } else { + return null; + } + } + + private static ClaimPartyEntity getClaimParty(ClaimEntity claim, UUID partyId) { + return claim.getClaimParties().stream() + .filter(claimPartyEntity -> partyId.equals(claimPartyEntity.getParty().getId())) + .findFirst() + .orElseThrow(() -> new PartyNotFoundException("Party not found")); + } +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java index a0f1ffcebd..4fa06921b1 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java @@ -22,12 +22,15 @@ import uk.gov.hmcts.reform.pcs.ccd.domain.enforcetheorder.warrantofrestitution.EvidenceDocumentType; import uk.gov.hmcts.reform.pcs.ccd.domain.enforcetheorder.warrantofrestitution.EvidenceOfDefendants; import uk.gov.hmcts.reform.pcs.ccd.domain.wales.OccupationLicenceDetailsWales; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.DocumentEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.GenAppEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.respondpossessionclaim.DefendantResponseEntity; import uk.gov.hmcts.reform.pcs.ccd.repository.DocumentRepository; import uk.gov.hmcts.reform.pcs.ccd.util.ListValueUtils; +import uk.gov.hmcts.reform.pcs.exception.ClaimNotFoundException; import java.util.ArrayList; import java.util.Collections; @@ -44,6 +47,7 @@ public class DocumentService { private final DocumentRepository documentRepository; private final DocumentIdExtractor documentIdExtractor; + private final DocumentNameService documentNameService; public List createAllDocuments(PCSCase pcsCase) { @@ -186,7 +190,8 @@ private DocumentType mapAdditionalDocumentTypeToDocumentType(AdditionalDocumentT public List createAdditionalDocumentsForParty( List> uploadedDocuments, PcsCaseEntity pcsCase, - PartyEntity party + PartyEntity party, + GenAppEntity selectedGenApp ) { if (CollectionUtils.isEmpty(uploadedDocuments)) { log.info("No additional documents to save for case {}", pcsCase.getCaseReference()); @@ -198,22 +203,32 @@ public List createAdditionalDocumentsForParty( .filter(Objects::nonNull) .collect(Collectors.toSet()); + ClaimEntity mainClaim = getMainClaim(pcsCase); + String applicationsCategoryId = CaseFileCategory.APPLICATIONS.getId(); + List documentEntities = uploadedDocuments.stream() .map(ListValue::getValue) .filter(Objects::nonNull) .filter(uploaded -> uploaded.getDocument() != null) .filter(uploaded -> !existingUrls.contains(uploaded.getDocument().getUrl())) - .map(uploaded -> DocumentEntity.builder() - .pcsCase(pcsCase) - .party(party) - .url(uploaded.getDocument().getUrl()) - .fileName(uploaded.getDocument().getFilename()) - .displayFileName(uploaded.getDocument().getFilename()) - .binaryUrl(uploaded.getDocument().getBinaryUrl()) - .contentType(uploaded.getContentType()) - .size(uploaded.getSizeInBytes()) - .type(DocumentType.OTHER) - .build()) + .map(uploaded -> { + String originalFilename = uploaded.getDocument().getFilename(); + String renamed = (selectedGenApp != null) + ? documentNameService.appendGenAppPostfix(originalFilename, selectedGenApp, mainClaim, party.getId()) + : documentNameService.appendPartyPostfix(originalFilename, mainClaim, party.getId()); + return DocumentEntity.builder() + .pcsCase(pcsCase) + .party(party) + .generalApplication(selectedGenApp) + .url(uploaded.getDocument().getUrl()) + .fileName(renamed) + .binaryUrl(uploaded.getDocument().getBinaryUrl()) + .contentType(uploaded.getContentType()) + .size(uploaded.getSizeInBytes()) + .type(DocumentType.OTHER) + .categoryId(selectedGenApp != null ? applicationsCategoryId : null) + .build(); + }) .toList(); if (documentEntities.isEmpty()) { @@ -228,6 +243,12 @@ public List createAdditionalDocumentsForParty( return saved; } + private static ClaimEntity getMainClaim(PcsCaseEntity pcsCase) { + return pcsCase.getClaims().stream() + .findFirst() + .orElseThrow(() -> new ClaimNotFoundException(pcsCase.getCaseReference())); + } + public List createDefendantUploadedDocuments( List> defendantDocuments, DefendantResponseEntity defendantResponse, diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java index bfbad8fe0c..d2d0a9367c 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java @@ -70,7 +70,7 @@ void setUp() { } @Test - void shouldPersistUploadedDocumentsForCurrentParty() { + void shouldPersistUploadedDocumentsForCurrentPartyWithNoGenAppWhenNoneSelected() { UploadedDocument uploaded = UploadedDocument.builder() .document(Document.builder() .url("url-1").filename("file-1.pdf").binaryUrl("bin-1").build()) @@ -88,7 +88,60 @@ void shouldPersistUploadedDocumentsForCurrentParty() { callSubmitHandler(caseData); - verify(documentService).createAdditionalDocumentsForParty(uploadedDocs, pcsCaseEntity, currentParty); + verify(documentService).createAdditionalDocumentsForParty(uploadedDocs, pcsCaseEntity, currentParty, null); + } + + @Test + void shouldResolveSelectedGenAppAndPassToDocumentService() { + UUID selectedId = UUID.randomUUID(); + GenAppEntity selectedGenApp = mock(GenAppEntity.class); + when(selectedGenApp.getId()).thenReturn(selectedId); + when(selectedGenApp.getState()).thenReturn(GenAppState.SUBMITTED); + when(selectedGenApp.getApplicationSubmittedDate()).thenReturn(LocalDateTime.now()); + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(selectedGenApp)); + + UploadedDocument uploaded = UploadedDocument.builder() + .document(Document.builder().url("url-1").filename("f.pdf").binaryUrl("bin").build()) + .build(); + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(uploaded).build() + ); + + PCSCase caseData = PCSCase.builder() + .uploadedAdditionalDocuments(uploadedDocs) + .documentUploadDetails(DocumentUploadDetails.builder() + .selectedRelatedApplicationId(selectedId) + .build()) + .build(); + + PartyEntity currentParty = stubCurrentUserParty(); + + callSubmitHandler(caseData); + + verify(documentService).createAdditionalDocumentsForParty( + uploadedDocs, pcsCaseEntity, currentParty, selectedGenApp); + } + + @Test + void shouldPassNullGenAppWhenSelectedIdDoesNotMatchAnyVisibleApp() { + UUID strayId = UUID.randomUUID(); + GenAppEntity otherGenApp = mock(GenAppEntity.class); + when(otherGenApp.getId()).thenReturn(UUID.randomUUID()); + when(otherGenApp.getState()).thenReturn(GenAppState.SUBMITTED); + when(otherGenApp.getApplicationSubmittedDate()).thenReturn(LocalDateTime.now()); + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(otherGenApp)); + + PCSCase caseData = PCSCase.builder() + .documentUploadDetails(DocumentUploadDetails.builder() + .selectedRelatedApplicationId(strayId) + .build()) + .build(); + + PartyEntity currentParty = stubCurrentUserParty(); + + callSubmitHandler(caseData); + + verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty, null); } @Test @@ -98,7 +151,15 @@ void shouldDelegateEvenWhenNoDocumentsSent() { callSubmitHandler(caseData); - verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty); + verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty, null); + } + + private Set setOf(GenAppEntity... entities) { + Set set = new HashSet<>(); + for (GenAppEntity entity : entities) { + set.add(entity); + } + return set; } private PartyEntity stubCurrentUserParty() { @@ -202,7 +263,7 @@ void shouldSortOptionsByLatestSubmittedDateDescending() { } @Test - void shouldUseLatestSubmittedDatePerCategoryWhenMultipleGenAppsShareAType() { + void shouldEmitOneOptionPerVisibleGenAppEvenWithinTheSameCategory() { LocalDateTime older = LocalDateTime.now().minusDays(5); LocalDateTime newer = LocalDateTime.now(); GenAppEntity olderAdjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, older); @@ -212,13 +273,29 @@ void shouldUseLatestSubmittedDatePerCategoryWhenMultipleGenAppsShareAType() { PCSCase result = callStartHandler(PCSCase.builder().build()); + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()) + .hasSize(2) + .extracting(option -> option.getValue().getSubmittedDate()) + .containsExactly(newer, older); + } + + @Test + void shouldStampOptionWithGenAppIdAndUseItAsListValueId() { + LocalDateTime submittedDate = LocalDateTime.now(); + GenAppEntity adjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, submittedDate); + UUID genAppId = UUID.randomUUID(); + when(adjourn.getId()).thenReturn(genAppId); + + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(adjourn)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()) .hasSize(1) .first() .satisfies(option -> { - assertThat(option.getValue().getCategory()) - .isEqualTo(DocumentUploadCategory.ADJOURN_HEARING_APPLICATION); - assertThat(option.getValue().getSubmittedDate()).isEqualTo(newer); + assertThat(option.getId()).isEqualTo(genAppId.toString()); + assertThat(option.getValue().getGenAppId()).isEqualTo(genAppId); }); } @@ -239,6 +316,7 @@ void shouldNotSurfaceSuspendCategoryWhileGenAppTypeSuspendIsAbsent() { private GenAppEntity stubGenApp(GenAppType type, GenAppState state, LocalDateTime submittedDate) { GenAppEntity entity = mock(GenAppEntity.class); + lenient().when(entity.getId()).thenReturn(UUID.randomUUID()); lenient().when(entity.getType()).thenReturn(type); lenient().when(entity.getState()).thenReturn(state); lenient().when(entity.getApplicationSubmittedDate()).thenReturn(submittedDate); diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java new file mode 100644 index 0000000000..cf225d775a --- /dev/null +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java @@ -0,0 +1,110 @@ +package uk.gov.hmcts.reform.pcs.ccd.service.document; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.GenAppEntity; +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.exception.PartyNotFoundException; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class DocumentNameServiceTest { + + private DocumentNameService underTest; + + @BeforeEach + void setUp() { + underTest = new DocumentNameService(); + } + + @Test + void appendGenAppPostfixIncludesRankAndPartyLabel() { + UUID partyId = UUID.randomUUID(); + List parties = List.of(claimPartyOf(partyId, PartyRole.DEFENDANT, 1)); + ClaimEntity mainClaim = mock(ClaimEntity.class); + when(mainClaim.getClaimParties()).thenReturn(parties); + + GenAppEntity genApp = mock(GenAppEntity.class); + when(genApp.getRank()).thenReturn(2); + + String renamed = underTest.appendGenAppPostfix("witness statement.pdf", genApp, mainClaim, partyId); + + assertThat(renamed).isEqualTo("witness statement GA2 - Defendant 1.pdf"); + } + + @Test + void appendGenAppPostfixSupportsClaimantRole() { + UUID partyId = UUID.randomUUID(); + List parties = List.of(claimPartyOf(partyId, PartyRole.CLAIMANT, 3)); + ClaimEntity mainClaim = mock(ClaimEntity.class); + when(mainClaim.getClaimParties()).thenReturn(parties); + + GenAppEntity genApp = mock(GenAppEntity.class); + when(genApp.getRank()).thenReturn(1); + + String renamed = underTest.appendGenAppPostfix("evidence.docx", genApp, mainClaim, partyId); + + assertThat(renamed).isEqualTo("evidence GA1 - Claimant 3.docx"); + } + + @Test + void appendGenAppPostfixOmitsExtensionWhenAbsent() { + UUID partyId = UUID.randomUUID(); + List parties = List.of(claimPartyOf(partyId, PartyRole.DEFENDANT, 1)); + ClaimEntity mainClaim = mock(ClaimEntity.class); + when(mainClaim.getClaimParties()).thenReturn(parties); + GenAppEntity genApp = mock(GenAppEntity.class); + when(genApp.getRank()).thenReturn(1); + + String renamed = underTest.appendGenAppPostfix("README", genApp, mainClaim, partyId); + + assertThat(renamed).isEqualTo("README GA1 - Defendant 1"); + } + + @Test + void appendGenAppPostfixReturnsNullWhenFilenameIsNull() { + assertThat(underTest.appendGenAppPostfix(null, mock(GenAppEntity.class), mock(ClaimEntity.class), UUID.randomUUID())) + .isNull(); + } + + @Test + void appendPartyPostfixAppendsDefendantLabelWithoutGaSegment() { + UUID partyId = UUID.randomUUID(); + List parties = List.of(claimPartyOf(partyId, PartyRole.DEFENDANT, 1)); + ClaimEntity mainClaim = mock(ClaimEntity.class); + when(mainClaim.getClaimParties()).thenReturn(parties); + + String renamed = underTest.appendPartyPostfix("statement.pdf", mainClaim, partyId); + + assertThat(renamed).isEqualTo("statement - Defendant 1.pdf"); + } + + @Test + void appendPartyPostfixThrowsWhenPartyNotPartOfTheClaim() { + List parties = List.of(claimPartyOf(UUID.randomUUID(), PartyRole.DEFENDANT, 1)); + ClaimEntity mainClaim = mock(ClaimEntity.class); + when(mainClaim.getClaimParties()).thenReturn(parties); + + assertThatThrownBy(() -> underTest.appendPartyPostfix("statement.pdf", mainClaim, UUID.randomUUID())) + .isInstanceOf(PartyNotFoundException.class); + } + + private ClaimPartyEntity claimPartyOf(UUID partyId, PartyRole role, int rank) { + ClaimPartyEntity claimParty = mock(ClaimPartyEntity.class); + PartyEntity party = mock(PartyEntity.class); + when(party.getId()).thenReturn(partyId); + when(claimParty.getParty()).thenReturn(party); + when(claimParty.getRole()).thenReturn(role); + when(claimParty.getRank()).thenReturn(rank); + return claimParty; + } +} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java index 9967f5c320..648e482021 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java @@ -27,7 +27,9 @@ import uk.gov.hmcts.reform.pcs.ccd.domain.enforcetheorder.warrantofrestitution.EvidenceOfDefendants; import uk.gov.hmcts.reform.pcs.ccd.domain.enforcetheorder.warrantofrestitution.WarrantOfRestitutionDetails; import uk.gov.hmcts.reform.pcs.ccd.domain.wales.OccupationLicenceDetailsWales; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.DocumentEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.GenAppEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.respondpossessionclaim.DefendantResponseEntity; @@ -54,6 +56,8 @@ class DocumentServiceTest { private DocumentRepository documentRepository; @Mock private DocumentIdExtractor documentIdExtractor; + @Mock + private DocumentNameService documentNameService; @Captor private ArgumentCaptor> documentEntityListCaptor; @@ -61,7 +65,7 @@ class DocumentServiceTest { @BeforeEach void setUp() { - underTest = new DocumentService(documentRepository, documentIdExtractor); + underTest = new DocumentService(documentRepository, documentIdExtractor, documentNameService); } @Test @@ -761,11 +765,17 @@ void shouldSaveDefendantEvidenceWithNullMetadata() { } @Test - void shouldSaveAdditionalDocumentsForPartyAsOtherType() { + void shouldSaveAdditionalDocumentsForPartyAsOtherTypeWithPartyPostfixWhenNoGenAppSelected() { // Given PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); PartyEntity party = mock(PartyEntity.class); + UUID partyId = UUID.randomUUID(); + ClaimEntity mainClaim = mock(ClaimEntity.class); + when(party.getId()).thenReturn(partyId); when(pcsCase.getDocuments()).thenReturn(new ArrayList<>()); + when(pcsCase.getClaims()).thenReturn(List.of(mainClaim)); + when(documentNameService.appendPartyPostfix("file-new.pdf", mainClaim, partyId)) + .thenReturn("file-new - Defendant 1.pdf"); UploadedDocument uploaded = UploadedDocument.builder() .document(Document.builder() @@ -781,7 +791,7 @@ void shouldSaveAdditionalDocumentsForPartyAsOtherType() { when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); // When - underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party); + underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, null); // Then verify(documentRepository).saveAll(documentEntityListCaptor.capture()); @@ -790,9 +800,9 @@ void shouldSaveAdditionalDocumentsForPartyAsOtherType() { DocumentEntity entity = entities.getFirst(); assertThat(entity.getType()).isEqualTo(DocumentType.OTHER); assertThat(entity.getCategoryId()).isNull(); + assertThat(entity.getGeneralApplication()).isNull(); assertThat(entity.getUrl()).isEqualTo("url-new"); - assertThat(entity.getFileName()).isEqualTo("file-new.pdf"); - assertThat(entity.getDisplayFileName()).isEqualTo("file-new.pdf"); + assertThat(entity.getFileName()).isEqualTo("file-new - Defendant 1.pdf"); assertThat(entity.getBinaryUrl()).isEqualTo("bin-new"); assertThat(entity.getContentType()).isEqualTo("application/pdf"); assertThat(entity.getSize()).isEqualTo(123L); @@ -800,15 +810,59 @@ void shouldSaveAdditionalDocumentsForPartyAsOtherType() { assertThat(entity.getParty()).isSameAs(party); } + @Test + void shouldAttachGenAppAndApplicationsCategoryWhenGenAppSelected() { + // Given + PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); + PartyEntity party = mock(PartyEntity.class); + UUID partyId = UUID.randomUUID(); + ClaimEntity mainClaim = mock(ClaimEntity.class); + GenAppEntity selectedGenApp = mock(GenAppEntity.class); + when(party.getId()).thenReturn(partyId); + when(pcsCase.getDocuments()).thenReturn(new ArrayList<>()); + when(pcsCase.getClaims()).thenReturn(List.of(mainClaim)); + when(documentNameService.appendGenAppPostfix("file-new.pdf", selectedGenApp, mainClaim, partyId)) + .thenReturn("file-new GA1 - Defendant 1.pdf"); + + UploadedDocument uploaded = UploadedDocument.builder() + .document(Document.builder() + .url("url-new").filename("file-new.pdf").binaryUrl("bin-new").build()) + .contentType("application/pdf") + .sizeInBytes(123L) + .build(); + + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(uploaded).build() + ); + + when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); + + // When + underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, selectedGenApp); + + // Then + verify(documentRepository).saveAll(documentEntityListCaptor.capture()); + List entities = documentEntityListCaptor.getValue(); + assertThat(entities).hasSize(1); + DocumentEntity entity = entities.getFirst(); + assertThat(entity.getType()).isEqualTo(DocumentType.OTHER); + assertThat(entity.getCategoryId()).isEqualTo(CaseFileCategory.APPLICATIONS.getId()); + assertThat(entity.getGeneralApplication()).isSameAs(selectedGenApp); + assertThat(entity.getFileName()).isEqualTo("file-new GA1 - Defendant 1.pdf"); + } + @Test void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { // Given PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); + UUID partyId = UUID.randomUUID(); + ClaimEntity mainClaim = mock(ClaimEntity.class); DocumentEntity existing = DocumentEntity.builder().url("url-existing").build(); List existingDocs = new ArrayList<>(); existingDocs.add(existing); when(pcsCase.getDocuments()).thenReturn(existingDocs); + when(pcsCase.getClaims()).thenReturn(List.of(mainClaim)); UploadedDocument duplicate = UploadedDocument.builder() .document(Document.builder() @@ -827,9 +881,12 @@ void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); PartyEntity party = mock(PartyEntity.class); + when(party.getId()).thenReturn(partyId); + when(documentNameService.appendPartyPostfix("new.pdf", mainClaim, partyId)) + .thenReturn("new - Defendant 1.pdf"); // When - underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party); + underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, null); // Then verify(documentRepository).saveAll(documentEntityListCaptor.capture()); @@ -847,6 +904,7 @@ void shouldNotCallRepositoryWhenAllAdditionalDocumentsAreDuplicates() { List existingDocs = new ArrayList<>(); existingDocs.add(DocumentEntity.builder().url("url-existing").build()); when(pcsCase.getDocuments()).thenReturn(existingDocs); + when(pcsCase.getClaims()).thenReturn(List.of(mock(ClaimEntity.class))); UploadedDocument duplicate = UploadedDocument.builder() .document(Document.builder() @@ -858,7 +916,7 @@ void shouldNotCallRepositoryWhenAllAdditionalDocumentsAreDuplicates() { ); // When - underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party); + underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, null); // Then verify(documentRepository, never()).saveAll(anyList()); @@ -871,8 +929,8 @@ void shouldReturnEmptyListWhenAdditionalDocumentsInputIsNullOrEmpty() { PartyEntity party = mock(PartyEntity.class); // When - underTest.createAdditionalDocumentsForParty(null, pcsCase, party); - underTest.createAdditionalDocumentsForParty(Collections.emptyList(), pcsCase, party); + underTest.createAdditionalDocumentsForParty(null, pcsCase, party, null); + underTest.createAdditionalDocumentsForParty(Collections.emptyList(), pcsCase, party, null); // Then verify(documentRepository, never()).saveAll(anyList()); From 86e251e35800f47a44f952fd96332532b5b8c90f Mon Sep 17 00:00:00 2001 From: fudge88 Date: Tue, 26 May 2026 12:34:08 +0100 Subject: [PATCH 07/13] uuid to string type --- .../documentupload/DocumentUploadDetails.java | 3 +-- .../RelatedApplicationOption.java | 3 +-- .../ccd/event/citizen/UploadDocuments.java | 9 ++++++-- .../event/citizen/UploadDocumentsTest.java | 21 ++++++++++++++++--- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java index e58423af58..e97c6239f7 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/DocumentUploadDetails.java @@ -12,7 +12,6 @@ import uk.gov.hmcts.reform.pcs.ccd.accesscontrol.DefendantAccess; import java.util.List; -import java.util.UUID; @Builder @Data @@ -37,5 +36,5 @@ public class DocumentUploadDetails { access = {DefendantAccess.class}, searchable = false ) - private UUID selectedRelatedApplicationId; + private String selectedRelatedApplicationId; } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java index 2c45db52f2..2d2cba98f7 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/domain/documentupload/RelatedApplicationOption.java @@ -8,7 +8,6 @@ import lombok.NoArgsConstructor; import java.time.LocalDateTime; -import java.util.UUID; @Builder @Data @@ -17,7 +16,7 @@ @JsonInclude(Include.NON_NULL) public class RelatedApplicationOption { - private UUID genAppId; + private String genAppId; private DocumentUploadCategory category; private LocalDateTime submittedDate; } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java index 96cea64e20..76fb05f64a 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java @@ -103,7 +103,7 @@ private ListValue toOption(GenAppEntity genApp) { return null; } RelatedApplicationOption option = RelatedApplicationOption.builder() - .genAppId(genApp.getId()) + .genAppId(genApp.getId().toString()) .category(category) .submittedDate(genApp.getApplicationSubmittedDate()) .build(); @@ -147,7 +147,12 @@ private GenAppEntity resolveSelectedGenApp(PCSCase caseData, PcsCaseEntity pcsCa if (details == null || details.getSelectedRelatedApplicationId() == null) { return null; } - UUID selectedId = details.getSelectedRelatedApplicationId(); + UUID selectedId; + try { + selectedId = UUID.fromString(details.getSelectedRelatedApplicationId()); + } catch (IllegalArgumentException e) { + return null; + } return visibleGenApps(pcsCaseEntity) .filter(genApp -> selectedId.equals(genApp.getId())) .findFirst() diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java index d2d0a9367c..a8744385b0 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java @@ -110,7 +110,7 @@ void shouldResolveSelectedGenAppAndPassToDocumentService() { PCSCase caseData = PCSCase.builder() .uploadedAdditionalDocuments(uploadedDocs) .documentUploadDetails(DocumentUploadDetails.builder() - .selectedRelatedApplicationId(selectedId) + .selectedRelatedApplicationId(selectedId.toString()) .build()) .build(); @@ -133,7 +133,22 @@ void shouldPassNullGenAppWhenSelectedIdDoesNotMatchAnyVisibleApp() { PCSCase caseData = PCSCase.builder() .documentUploadDetails(DocumentUploadDetails.builder() - .selectedRelatedApplicationId(strayId) + .selectedRelatedApplicationId(strayId.toString()) + .build()) + .build(); + + PartyEntity currentParty = stubCurrentUserParty(); + + callSubmitHandler(caseData); + + verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty, null); + } + + @Test + void shouldPassNullGenAppWhenSelectedIdIsNotAValidUuid() { + PCSCase caseData = PCSCase.builder() + .documentUploadDetails(DocumentUploadDetails.builder() + .selectedRelatedApplicationId("not-a-uuid") .build()) .build(); @@ -295,7 +310,7 @@ void shouldStampOptionWithGenAppIdAndUseItAsListValueId() { .first() .satisfies(option -> { assertThat(option.getId()).isEqualTo(genAppId.toString()); - assertThat(option.getValue().getGenAppId()).isEqualTo(genAppId); + assertThat(option.getValue().getGenAppId()).isEqualTo(genAppId.toString()); }); } From 14690ca01f48072983ee115b3a0dcad579de110a Mon Sep 17 00:00:00 2001 From: fudge88 Date: Tue, 26 May 2026 12:49:59 +0100 Subject: [PATCH 08/13] checkstyle fix --- .../ccd/service/document/DocumentService.java | 3 ++- .../document/DocumentNameServiceTest.java | 3 ++- .../service/document/DocumentServiceTest.java | 17 ++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java index 4fa06921b1..2416739128 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java @@ -214,7 +214,8 @@ public List createAdditionalDocumentsForParty( .map(uploaded -> { String originalFilename = uploaded.getDocument().getFilename(); String renamed = (selectedGenApp != null) - ? documentNameService.appendGenAppPostfix(originalFilename, selectedGenApp, mainClaim, party.getId()) + ? documentNameService.appendGenAppPostfix( + originalFilename, selectedGenApp, mainClaim, party.getId()) : documentNameService.appendPartyPostfix(originalFilename, mainClaim, party.getId()); return DocumentEntity.builder() .pcsCase(pcsCase) diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java index cf225d775a..9faf9673ce 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java @@ -72,7 +72,8 @@ void appendGenAppPostfixOmitsExtensionWhenAbsent() { @Test void appendGenAppPostfixReturnsNullWhenFilenameIsNull() { - assertThat(underTest.appendGenAppPostfix(null, mock(GenAppEntity.class), mock(ClaimEntity.class), UUID.randomUUID())) + assertThat(underTest.appendGenAppPostfix( + null, mock(GenAppEntity.class), mock(ClaimEntity.class), UUID.randomUUID())) .isNull(); } diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java index 648e482021..058b48a703 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java @@ -855,7 +855,6 @@ void shouldAttachGenAppAndApplicationsCategoryWhenGenAppSelected() { void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { // Given PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); - UUID partyId = UUID.randomUUID(); ClaimEntity mainClaim = mock(ClaimEntity.class); DocumentEntity existing = DocumentEntity.builder().url("url-existing").build(); @@ -873,18 +872,18 @@ void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { .url("url-new").filename("new.pdf").binaryUrl("bin-new").build()) .build(); - List> uploadedDocs = List.of( - ListValue.builder().id("1").value(duplicate).build(), - ListValue.builder().id("2").value(fresh).build() - ); - when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); PartyEntity party = mock(PartyEntity.class); - when(party.getId()).thenReturn(partyId); - when(documentNameService.appendPartyPostfix("new.pdf", mainClaim, partyId)) + when(party.getId()).thenReturn(UUID.randomUUID()); + when(documentNameService.appendPartyPostfix("new.pdf", mainClaim, party.getId())) .thenReturn("new - Defendant 1.pdf"); + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(duplicate).build(), + ListValue.builder().id("2").value(fresh).build() + ); + // When underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, null); @@ -899,7 +898,6 @@ void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { void shouldNotCallRepositoryWhenAllAdditionalDocumentsAreDuplicates() { // Given PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); - PartyEntity party = mock(PartyEntity.class); List existingDocs = new ArrayList<>(); existingDocs.add(DocumentEntity.builder().url("url-existing").build()); @@ -911,6 +909,7 @@ void shouldNotCallRepositoryWhenAllAdditionalDocumentsAreDuplicates() { .url("url-existing").filename("dup.pdf").binaryUrl("bin-dup").build()) .build(); + PartyEntity party = mock(PartyEntity.class); List> uploadedDocs = List.of( ListValue.builder().id("1").value(duplicate).build() ); From 925062856ce8715090a0c42f3c27e2715e70f050 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Tue, 26 May 2026 15:43:01 +0100 Subject: [PATCH 09/13] supress sonar warnings fixed test --- .../java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java | 1 + .../reform/pcs/ccd/event/citizen/UploadDocumentsTest.java | 7 ++++--- .../pcs/ccd/service/document/DocumentNameServiceTest.java | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java index 20a4b0021f..1ed6e1a3b2 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/EventId.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.reform.pcs.ccd.event; +@SuppressWarnings("java:S115") // constant names match the camelCase CCD event IDs registered via .name() public enum EventId { createPossessionClaim, diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java index a8744385b0..a90e6ef66f 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java @@ -319,12 +319,13 @@ void shouldNotSurfaceSuspendCategoryWhileGenAppTypeSuspendIsAbsent() { // SUSPEND was removed from GenAppType by PR #1804. Until it is restored, the // SUSPEND_EVICTION_APPLICATION category must be filtered out so we don't render // a radio backed by no data. - when(pcsCaseEntity.getGenApps()).thenReturn(new HashSet<>()); - PCSCase caseData = PCSCase.builder().build(); + GenAppEntity adjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, LocalDateTime.now()); + when(pcsCaseEntity.getGenApps()).thenReturn(setOf(adjourn)); - PCSCase result = callStartHandler(caseData); + PCSCase result = callStartHandler(PCSCase.builder().build()); assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()) + .isNotEmpty() .extracting(option -> option.getValue().getCategory()) .doesNotContain(DocumentUploadCategory.SUSPEND_EVICTION_APPLICATION); } diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java index 9faf9673ce..aa3623d64f 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentNameServiceTest.java @@ -94,8 +94,9 @@ void appendPartyPostfixThrowsWhenPartyNotPartOfTheClaim() { List parties = List.of(claimPartyOf(UUID.randomUUID(), PartyRole.DEFENDANT, 1)); ClaimEntity mainClaim = mock(ClaimEntity.class); when(mainClaim.getClaimParties()).thenReturn(parties); + UUID strayPartyId = UUID.randomUUID(); - assertThatThrownBy(() -> underTest.appendPartyPostfix("statement.pdf", mainClaim, UUID.randomUUID())) + assertThatThrownBy(() -> underTest.appendPartyPostfix("statement.pdf", mainClaim, strayPartyId)) .isInstanceOf(PartyNotFoundException.class); } From f0636596c14268c11635c40577d54c2629c95828 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Wed, 3 Jun 2026 11:56:20 +0100 Subject: [PATCH 10/13] null guard --- .../ccd/event/citizen/UploadDocuments.java | 2 +- .../ccd/service/document/DocumentService.java | 5 +- .../event/citizen/UploadDocumentsTest.java | 74 +++++++++++-------- .../service/document/DocumentServiceTest.java | 12 +-- 4 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java index 76fb05f64a..ca328a7ec4 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocuments.java @@ -132,7 +132,7 @@ private SubmitResponse submit(EventPayload eventPayload) PartyEntity uploadingParty = getCurrentPartyEntity(caseReference); GenAppEntity selectedGenApp = resolveSelectedGenApp(caseData, pcsCaseEntity); - documentService.createAdditionalDocumentsForParty( + documentService.linkAdditionalDocumentsToCase( caseData.getUploadedAdditionalDocuments(), pcsCaseEntity, uploadingParty, diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java index 074a7baef1..7dce70cb77 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentService.java @@ -170,6 +170,9 @@ private List createDocumentEntities( } public DocumentType mapAdditionalDocumentTypeToDocumentType(AdditionalDocumentType additionalType) { + if (additionalType == null) { + return null; + } return switch (additionalType) { case WITNESS_STATEMENT -> DocumentType.WITNESS_STATEMENT; case RENT_STATEMENT -> DocumentType.RENT_STATEMENT; @@ -187,7 +190,7 @@ public DocumentType mapAdditionalDocumentTypeToDocumentType(AdditionalDocumentTy }; } - public List createAdditionalDocumentsForParty( + public List linkAdditionalDocumentsToCase( List> uploadedDocuments, PcsCaseEntity pcsCase, PartyEntity party, diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java index a90e6ef66f..a667233e9a 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java @@ -88,7 +88,7 @@ void shouldPersistUploadedDocumentsForCurrentPartyWithNoGenAppWhenNoneSelected() callSubmitHandler(caseData); - verify(documentService).createAdditionalDocumentsForParty(uploadedDocs, pcsCaseEntity, currentParty, null); + verify(documentService).linkAdditionalDocumentsToCase(uploadedDocs, pcsCaseEntity, currentParty, null); } @Test @@ -98,7 +98,7 @@ void shouldResolveSelectedGenAppAndPassToDocumentService() { when(selectedGenApp.getId()).thenReturn(selectedId); when(selectedGenApp.getState()).thenReturn(GenAppState.SUBMITTED); when(selectedGenApp.getApplicationSubmittedDate()).thenReturn(LocalDateTime.now()); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(selectedGenApp)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(selectedGenApp)); UploadedDocument uploaded = UploadedDocument.builder() .document(Document.builder().url("url-1").filename("f.pdf").binaryUrl("bin").build()) @@ -118,7 +118,7 @@ void shouldResolveSelectedGenAppAndPassToDocumentService() { callSubmitHandler(caseData); - verify(documentService).createAdditionalDocumentsForParty( + verify(documentService).linkAdditionalDocumentsToCase( uploadedDocs, pcsCaseEntity, currentParty, selectedGenApp); } @@ -129,7 +129,7 @@ void shouldPassNullGenAppWhenSelectedIdDoesNotMatchAnyVisibleApp() { when(otherGenApp.getId()).thenReturn(UUID.randomUUID()); when(otherGenApp.getState()).thenReturn(GenAppState.SUBMITTED); when(otherGenApp.getApplicationSubmittedDate()).thenReturn(LocalDateTime.now()); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(otherGenApp)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(otherGenApp)); PCSCase caseData = PCSCase.builder() .documentUploadDetails(DocumentUploadDetails.builder() @@ -141,7 +141,7 @@ void shouldPassNullGenAppWhenSelectedIdDoesNotMatchAnyVisibleApp() { callSubmitHandler(caseData); - verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty, null); + verify(documentService).linkAdditionalDocumentsToCase(null, pcsCaseEntity, currentParty, null); } @Test @@ -156,25 +156,30 @@ void shouldPassNullGenAppWhenSelectedIdIsNotAValidUuid() { callSubmitHandler(caseData); - verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty, null); + verify(documentService).linkAdditionalDocumentsToCase(null, pcsCaseEntity, currentParty, null); } @Test - void shouldDelegateEvenWhenNoDocumentsSent() { - PCSCase caseData = PCSCase.builder().build(); + void shouldPassNullGenAppWhenDocumentUploadDetailsHasNoSelection() { + PCSCase caseData = PCSCase.builder() + .documentUploadDetails(DocumentUploadDetails.builder().build()) + .build(); + PartyEntity currentParty = stubCurrentUserParty(); callSubmitHandler(caseData); - verify(documentService).createAdditionalDocumentsForParty(null, pcsCaseEntity, currentParty, null); + verify(documentService).linkAdditionalDocumentsToCase(null, pcsCaseEntity, currentParty, null); } - private Set setOf(GenAppEntity... entities) { - Set set = new HashSet<>(); - for (GenAppEntity entity : entities) { - set.add(entity); - } - return set; + @Test + void shouldDelegateEvenWhenNoDocumentsSent() { + PCSCase caseData = PCSCase.builder().build(); + PartyEntity currentParty = stubCurrentUserParty(); + + callSubmitHandler(caseData); + + verify(documentService).linkAdditionalDocumentsToCase(null, pcsCaseEntity, currentParty, null); } private PartyEntity stubCurrentUserParty() { @@ -217,7 +222,7 @@ void shouldIncludeAdjournCategoryWhenAdjournGenAppExists() { LocalDateTime submittedDate = LocalDateTime.now(); GenAppEntity adjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, submittedDate); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(adjourn)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(adjourn)); PCSCase result = callStartHandler(PCSCase.builder().build()); @@ -225,7 +230,7 @@ void shouldIncludeAdjournCategoryWhenAdjournGenAppExists() { assertThat(details.getRelatedApplicationOptions()) .extracting(option -> option.getValue().getCategory()) .containsExactly(DocumentUploadCategory.ADJOURN_HEARING_APPLICATION); - assertThat(details.getRelatedApplicationOptions().get(0).getValue().getSubmittedDate()) + assertThat(details.getRelatedApplicationOptions().getFirst().getValue().getSubmittedDate()) .isEqualTo(submittedDate); assertThat(details.getShowRelatedApplicationsPage()).isEqualTo(YesOrNo.YES); } @@ -235,7 +240,7 @@ void shouldIncludeGenAppsInPendingSubmissionState() { LocalDateTime submittedDate = LocalDateTime.now(); GenAppEntity pending = stubGenApp(GenAppType.SET_ASIDE, GenAppState.PENDING_SUBMISSION, submittedDate); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(pending)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(pending)); PCSCase result = callStartHandler(PCSCase.builder().build()); @@ -249,7 +254,22 @@ void shouldExcludeGenAppsWithNoState() { // Defensive: a genApp with a null state must not surface a radio option. GenAppEntity stateless = stubGenApp(GenAppType.ADJOURN, null, LocalDateTime.now()); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(stateless)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(stateless)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()).isEmpty(); + assertThat(result.getDocumentUploadDetails().getShowRelatedApplicationsPage()) + .isEqualTo(YesOrNo.NO); + } + + @Test + void shouldExcludeGenAppsWithNoType() { + // A genApp with a null type means mapGenAppTypeToCategory returns null, + // toOption returns null, and the option is filtered out. + GenAppEntity typeless = stubGenApp(null, GenAppState.SUBMITTED, LocalDateTime.now()); + + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(typeless)); PCSCase result = callStartHandler(PCSCase.builder().build()); @@ -265,7 +285,7 @@ void shouldSortOptionsByLatestSubmittedDateDescending() { GenAppEntity midSetAside = stubGenApp(GenAppType.SET_ASIDE, GenAppState.SUBMITTED, now.minusDays(3)); GenAppEntity newestGeneral = stubGenApp(GenAppType.SOMETHING_ELSE, GenAppState.SUBMITTED, now); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(oldestAdjourn, midSetAside, newestGeneral)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(oldestAdjourn, midSetAside, newestGeneral)); PCSCase result = callStartHandler(PCSCase.builder().build()); @@ -284,7 +304,7 @@ void shouldEmitOneOptionPerVisibleGenAppEvenWithinTheSameCategory() { GenAppEntity olderAdjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, older); GenAppEntity newerAdjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, newer); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(olderAdjourn, newerAdjourn)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(olderAdjourn, newerAdjourn)); PCSCase result = callStartHandler(PCSCase.builder().build()); @@ -301,7 +321,7 @@ void shouldStampOptionWithGenAppIdAndUseItAsListValueId() { UUID genAppId = UUID.randomUUID(); when(adjourn.getId()).thenReturn(genAppId); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(adjourn)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(adjourn)); PCSCase result = callStartHandler(PCSCase.builder().build()); @@ -320,7 +340,7 @@ void shouldNotSurfaceSuspendCategoryWhileGenAppTypeSuspendIsAbsent() { // SUSPEND_EVICTION_APPLICATION category must be filtered out so we don't render // a radio backed by no data. GenAppEntity adjourn = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, LocalDateTime.now()); - when(pcsCaseEntity.getGenApps()).thenReturn(setOf(adjourn)); + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(adjourn)); PCSCase result = callStartHandler(PCSCase.builder().build()); @@ -339,14 +359,6 @@ private GenAppEntity stubGenApp(GenAppType type, GenAppState state, LocalDateTim return entity; } - private Set setOf(GenAppEntity... entities) { - Set set = new HashSet<>(); - for (GenAppEntity entity : entities) { - set.add(entity); - } - return set; - } - @Test void shouldHandleNullGenAppsCollection() { when(pcsCaseEntity.getGenApps()).thenReturn(null); diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java index c55ba603b8..2431296a66 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java @@ -791,7 +791,7 @@ void shouldSaveAdditionalDocumentsForPartyAsOtherTypeWithPartyPostfixWhenNoGenAp when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); // When - underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, null); + underTest.linkAdditionalDocumentsToCase(uploadedDocs, pcsCase, party, null); // Then verify(documentRepository).saveAll(documentEntityListCaptor.capture()); @@ -838,7 +838,7 @@ void shouldAttachGenAppAndApplicationsCategoryWhenGenAppSelected() { when(documentRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0)); // When - underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, selectedGenApp); + underTest.linkAdditionalDocumentsToCase(uploadedDocs, pcsCase, party, selectedGenApp); // Then verify(documentRepository).saveAll(documentEntityListCaptor.capture()); @@ -885,7 +885,7 @@ void shouldSkipAdditionalDocumentsAlreadyPersistedByUrl() { ); // When - underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, null); + underTest.linkAdditionalDocumentsToCase(uploadedDocs, pcsCase, party, null); // Then verify(documentRepository).saveAll(documentEntityListCaptor.capture()); @@ -915,7 +915,7 @@ void shouldNotCallRepositoryWhenAllAdditionalDocumentsAreDuplicates() { ); // When - underTest.createAdditionalDocumentsForParty(uploadedDocs, pcsCase, party, null); + underTest.linkAdditionalDocumentsToCase(uploadedDocs, pcsCase, party, null); // Then verify(documentRepository, never()).saveAll(anyList()); @@ -928,8 +928,8 @@ void shouldReturnEmptyListWhenAdditionalDocumentsInputIsNullOrEmpty() { PartyEntity party = mock(PartyEntity.class); // When - underTest.createAdditionalDocumentsForParty(null, pcsCase, party, null); - underTest.createAdditionalDocumentsForParty(Collections.emptyList(), pcsCase, party, null); + underTest.linkAdditionalDocumentsToCase(null, pcsCase, party, null); + underTest.linkAdditionalDocumentsToCase(Collections.emptyList(), pcsCase, party, null); // Then verify(documentRepository, never()).saveAll(anyList()); From 39b86651fbb8f709d895350bcda3e4379a74b468 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Fri, 5 Jun 2026 16:06:41 +0100 Subject: [PATCH 11/13] updated appsubmitteddate --- .../event/citizen/UploadDocumentsTest.java | 13 +++ .../service/document/DocumentServiceTest.java | 85 +++++++------------ 2 files changed, 42 insertions(+), 56 deletions(-) diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java index a667233e9a..39016ad95e 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/event/citizen/UploadDocumentsTest.java @@ -278,6 +278,19 @@ void shouldExcludeGenAppsWithNoType() { .isEqualTo(YesOrNo.NO); } + @Test + void shouldExcludeGenAppsWithNoApplicationSubmittedDate() { + GenAppEntity undated = stubGenApp(GenAppType.ADJOURN, GenAppState.SUBMITTED, null); + + when(pcsCaseEntity.getGenApps()).thenReturn(Set.of(undated)); + + PCSCase result = callStartHandler(PCSCase.builder().build()); + + assertThat(result.getDocumentUploadDetails().getRelatedApplicationOptions()).isEmpty(); + assertThat(result.getDocumentUploadDetails().getShowRelatedApplicationsPage()) + .isEqualTo(YesOrNo.NO); + } + @Test void shouldSortOptionsByLatestSubmittedDateDescending() { LocalDateTime now = LocalDateTime.now(); diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java index 2431296a66..061bd28ed9 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/ccd/service/document/DocumentServiceTest.java @@ -35,6 +35,7 @@ import uk.gov.hmcts.reform.pcs.ccd.entity.respondpossessionclaim.DefendantResponseEntity; import uk.gov.hmcts.reform.pcs.ccd.repository.DocumentRepository; import uk.gov.hmcts.reform.pcs.ccd.util.ListValueUtils; +import uk.gov.hmcts.reform.pcs.exception.ClaimNotFoundException; import java.util.ArrayList; import java.util.Collections; @@ -42,6 +43,7 @@ import java.util.UUID; import java.util.stream.Stream; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.mock; @@ -935,6 +937,33 @@ void shouldReturnEmptyListWhenAdditionalDocumentsInputIsNullOrEmpty() { verify(documentRepository, never()).saveAll(anyList()); } + @Test + void shouldThrowClaimNotFoundExceptionWhenCaseHasNoClaims() { + // Given + long caseReference = 1234567890123456L; + PcsCaseEntity pcsCase = mock(PcsCaseEntity.class); + PartyEntity party = mock(PartyEntity.class); + when(pcsCase.getDocuments()).thenReturn(new ArrayList<>()); + when(pcsCase.getClaims()).thenReturn(Collections.emptyList()); + when(pcsCase.getCaseReference()).thenReturn(caseReference); + + UploadedDocument uploaded = UploadedDocument.builder() + .document(Document.builder() + .url("url-new").filename("file-new.pdf").binaryUrl("bin-new").build()) + .build(); + List> uploadedDocs = List.of( + ListValue.builder().id("1").value(uploaded).build() + ); + + // When / Then + assertThatThrownBy(() -> + underTest.linkAdditionalDocumentsToCase(uploadedDocs, pcsCase, party, null)) + .isInstanceOf(ClaimNotFoundException.class) + .hasMessageContaining(String.valueOf(caseReference)); + + verify(documentRepository, never()).saveAll(anyList()); + } + private static Stream additionalDocumentCategoryScenarios() { return Stream.of( Arguments.of( @@ -957,60 +986,4 @@ private static Stream additionalDocumentCategoryScenarios() { ); } - private static Stream additionalDocumentTypeScenarios() { - return Stream.of( - Arguments.of( - AdditionalDocumentType.WITNESS_STATEMENT, - DocumentType.WITNESS_STATEMENT - ), - Arguments.of( - AdditionalDocumentType.RENT_STATEMENT, - DocumentType.RENT_STATEMENT - ), - Arguments.of( - AdditionalDocumentType.TENANCY_AGREEMENT, - DocumentType.TENANCY_AGREEMENT - ), - Arguments.of( - AdditionalDocumentType.CERTIFICATE_OF_SERVICE, - DocumentType.CERTIFICATE_OF_SERVICE - ), - Arguments.of( - AdditionalDocumentType.CORRESPONDENCE_FROM_DEFENDANT, - DocumentType.CORRESPONDENCE_FROM_DEFENDANT - ), - Arguments.of( - AdditionalDocumentType.CORRESPONDENCE_FROM_CLAIMANT, - DocumentType.CORRESPONDENCE_FROM_CLAIMANT - ), - Arguments.of( - AdditionalDocumentType.POSSESSION_NOTICE, - DocumentType.POSSESSION_NOTICE - ), - Arguments.of( - AdditionalDocumentType.NOTICE_FOR_SERVICE_OUT_OF_JURISDICTION, - DocumentType.NOTICE_FOR_SERVICE_OUT_OF_JURISDICTION - ), - Arguments.of( - AdditionalDocumentType.PHOTOGRAPHIC_EVIDENCE, - DocumentType.PHOTOGRAPHIC_EVIDENCE - ), - Arguments.of( - AdditionalDocumentType.INSPECTION_OR_REPORT, - DocumentType.INSPECTION_OR_REPORT - ), - Arguments.of( - AdditionalDocumentType.CERTIFICATE_OF_SUITABILITY_AS_LF, - DocumentType.CERTIFICATE_OF_SUITABILITY_AS_LF - ), - Arguments.of( - AdditionalDocumentType.LEGAL_AID_CERTIFICATE, - DocumentType.LEGAL_AID_CERTIFICATE - ), - Arguments.of( - AdditionalDocumentType.OTHER, - DocumentType.OTHER - ) - ); - } } From 9216b9232a7e1af233613c91da4abcd4e3db24e7 Mon Sep 17 00:00:00 2001 From: fudge88 Date: Mon, 8 Jun 2026 15:03:38 +0100 Subject: [PATCH 12/13] trigger CI From 41eda3bfbbefbeae214d257b80c2ed0eeec1f04b Mon Sep 17 00:00:00 2001 From: Marianne-Azzopardi Date: Wed, 10 Jun 2026 09:17:25 +0100 Subject: [PATCH 13/13] Trigger preview redeploy