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..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 @@ -719,6 +719,124 @@ 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); + + 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)); + + PartyEntity party = mock(PartyEntity.class); + + // 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(