Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DMP-2542 Unlink expired cases from media #2391

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import uk.gov.hmcts.darts.common.entity.EventEntity;
import uk.gov.hmcts.darts.common.entity.EventLinkedCaseEntity;
import uk.gov.hmcts.darts.common.entity.HearingEntity;
import uk.gov.hmcts.darts.common.entity.MediaEntity;
import uk.gov.hmcts.darts.common.entity.ProsecutorEntity;
import uk.gov.hmcts.darts.common.entity.TranscriptionCommentEntity;
import uk.gov.hmcts.darts.common.entity.TranscriptionEntity;
Expand All @@ -33,6 +34,7 @@
import uk.gov.hmcts.darts.testutils.PostgresIntegrationBase;
import uk.gov.hmcts.darts.testutils.stubs.EventLinkedCaseStub;
import uk.gov.hmcts.darts.testutils.stubs.EventStub;
import uk.gov.hmcts.darts.testutils.stubs.MediaLinkedCaseStub;
import uk.gov.hmcts.darts.testutils.stubs.TranscriptionStub;
import uk.gov.hmcts.darts.transcriptions.enums.TranscriptionStatusEnum;

Expand All @@ -59,6 +61,7 @@ class CaseExpiryDeletionAutomatedTaskITest extends PostgresIntegrationBase {

private final CaseExpiryDeletionAutomatedTask caseExpiryDeletionAutomatedTask;
private final EventLinkedCaseStub eventLinkedCaseStub;
private final MediaLinkedCaseStub mediaLinkedCaseStub;
private int caseIndex;

@Test
Expand All @@ -74,7 +77,7 @@ void positiveRetentionDatePassed() {

@Test
@DisplayName("Two cases linked to the same event, one case has passed retention date, the other has not. Event should not be anonymised")
void retentionDatePassedForOneCaseButNotAnotherEventNotAnoymised() {
void retentionDatePassedForOneCaseButNotAnotherEventNotAnonymised() {
CourtCaseEntity courtCase1 = createCase(-1, CaseRetentionStatus.COMPLETE);
CourtCaseEntity courtCase2 = createCase(-1, CaseRetentionStatus.PENDING);

Expand All @@ -90,7 +93,7 @@ void retentionDatePassedForOneCaseButNotAnotherEventNotAnoymised() {

@Test
@DisplayName("Two cases linked to the same event, both cases have passed retention date. Event should be anonymised")
void retentionDatePassedForBothCaseLinkedEventsAnoymised() {
void retentionDatePassedForBothCaseLinkedEventsAnonymised() {
CourtCaseEntity courtCase1 = createCase(-1, CaseRetentionStatus.COMPLETE);
CourtCaseEntity courtCase2 = createCase(-1, CaseRetentionStatus.COMPLETE);

Expand Down Expand Up @@ -148,6 +151,48 @@ void positiveMultipleToAnonymiseAndSomeNotTo() {
assertCase(courtCase5.getId(), false);
}

@Test
@DisplayName("Two cases linked to the same media, one case has passed retention date, the other has not. Media link should not be removed from hearings")
void retentionDatePassedForOneCaseButNotAnotherMediaLinkNotRemovedFromHearing() {
CourtCaseEntity courtCase1 = createCase(-1, CaseRetentionStatus.COMPLETE);
CourtCaseEntity courtCase2 = createCase(1, CaseRetentionStatus.COMPLETE);

createMediaForHearing(courtCase1.getHearings().get(0));
// Link same media to second case
linkExistingMediaToHearing(courtCase1.getHearings().get(0).getMediaList(), courtCase2.getHearings().getFirst(), courtCase2);

caseExpiryDeletionAutomatedTask.preRunTask();
caseExpiryDeletionAutomatedTask.runTask();

List<HearingEntity> hearingsToAssert = List.of(courtCase1.getHearings().get(0), courtCase2.getHearings().get(0));

hearingsToAssert.forEach(h -> {
HearingEntity hearing = dartsDatabase.getHearingRepository().findByCaseIdWithMediaList(h.getCourtCase().getId()).get();
assertThat(hearing.getMediaList()).size().isEqualTo(1);
assertThat(hearing.getMediaList().getFirst().getId()).isEqualTo(1);
});
}

@Test
@DisplayName("Two cases linked to the same media, both cases have passed retention date. Media link should be removed from hearings")
void retentionDatePassedForBothCasesThenMediaLinkRemovedFromHearing() {
CourtCaseEntity courtCase1 = createCase(-1, CaseRetentionStatus.COMPLETE);
CourtCaseEntity courtCase2 = createCase(-1, CaseRetentionStatus.COMPLETE);

createMediaForHearing(courtCase1.getHearings().get(0));
// Link same media to second case
linkExistingMediaToHearing(courtCase1.getHearings().get(0).getMediaList(), courtCase2.getHearings().getFirst(), courtCase2);

caseExpiryDeletionAutomatedTask.preRunTask();
caseExpiryDeletionAutomatedTask.runTask();

List<HearingEntity> hearingsToAssert = List.of(courtCase1.getHearings().get(0), courtCase2.getHearings().get(0));

hearingsToAssert.forEach(h -> {
HearingEntity hearing = dartsDatabase.getHearingRepository().findByCaseIdWithMediaList(h.getCourtCase().getId()).get();
assertThat(hearing.getMediaList()).isEmpty();
});
}

private void assertCase(int caseId, boolean isAnonymised, int... excludeEventIds) {
transactionalUtil.executeInTransaction(() -> {
Expand Down Expand Up @@ -356,7 +401,6 @@ private CourtCaseEntity createCase(final long daysUntilRetention, final CaseRete
caseEntity.addProsecutor(createProsecutorEntity(caseEntity));

HearingEntity hearing = createHearing(caseEntity);
createHearing(caseEntity);

caseEntity = dartsDatabase.getCaseRepository().save(caseEntity);

Expand Down Expand Up @@ -392,6 +436,27 @@ private HearingEntity createHearing(CourtCaseEntity caseEntity) {
return hearingEntity;
}

private void createMediaForHearing(HearingEntity hearingEntity) {
MediaEntity mediaEntity = dartsDatabase.getMediaStub().createMediaEntity(hearingEntity.getCourtCase().getCourthouse().getCourthouseName(),
"1",
OffsetDateTime.parse("2024-01-01T00:00:00Z"),
OffsetDateTime.parse("2024-01-01T00:00:00Z"),
1,
"MP2");
hearingEntity.setMediaList(new ArrayList<>(List.of(mediaEntity)));
mediaLinkedCaseStub.createCaseLinkedMedia(mediaEntity, hearingEntity.getCourtCase());
dartsDatabase.getHearingRepository().save(hearingEntity);
}

private void linkExistingMediaToHearing(List<MediaEntity> mediaList, HearingEntity hearingEntity, CourtCaseEntity courtCase) {
transactionalUtil.executeInTransaction(() -> {
hearingEntity.setMediaList(mediaList);
dartsDatabase.getHearingRepository().save(hearingEntity);

mediaLinkedCaseStub.createCaseLinkedMedia(mediaList.get(0), courtCase);
});
}

private void createMediaRequest(HearingEntity hearingEntity) {
MediaRequestEntity mediaRequestEntity = dartsDatabase.getMediaRequestStub()
.createAndSaveMediaRequestEntity(hearingEntity.getCreatedBy(), hearingEntity);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package uk.gov.hmcts.darts.testutils.stubs;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import uk.gov.hmcts.darts.common.entity.CourtCaseEntity;
import uk.gov.hmcts.darts.common.entity.MediaEntity;
import uk.gov.hmcts.darts.common.entity.MediaLinkedCaseEntity;
import uk.gov.hmcts.darts.common.repository.MediaLinkedCaseRepository;

@Component
@RequiredArgsConstructor
public class MediaLinkedCaseStub {

private final MediaLinkedCaseRepository mediaLinkedCaseRepository;

public MediaLinkedCaseEntity createCaseLinkedMedia(MediaEntity media, CourtCaseEntity caseEntity) {
MediaLinkedCaseEntity mediaLinkedCaseEntity = new MediaLinkedCaseEntity();
mediaLinkedCaseEntity.setMedia(media);
mediaLinkedCaseEntity.setCourtCase(caseEntity);
return mediaLinkedCaseRepository.save(mediaLinkedCaseEntity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public interface HearingRepository extends JpaRepository<HearingEntity, Integer>
)
List<Integer> findHearingIdsByEventId(Integer eventId);

@Query("""
SELECT h FROM HearingEntity h
JOIN h.mediaList media
WHERE media.id = :mediaId
"""
)
List<HearingEntity> findHearingIdsByMediaId(Integer mediaId);

@Query("""
SELECT h FROM HearingEntity h, CourthouseEntity ch, CourtroomEntity cr, CourtCaseEntity case
WHERE ch.courthouseName = upper(:courthouse)
Expand Down Expand Up @@ -76,4 +84,13 @@ WHERE he.courtroom.id in (select courtroom.id from CourtroomEntity where courtho
List<HearingEntity> findHearingDetails(List<Integer> courthouseIds, String caseNumber,
String courtroomName,
LocalDate startDate, LocalDate endDate, Integer numberOfRecords);

@Query("""
SELECT hearing
FROM HearingEntity hearing, CourtCaseEntity case
LEFT JOIN FETCH hearing.mediaList
WHERE case.id = :caseId
AND hearing.courtCase = case
""")
Optional<HearingEntity> findByCaseIdWithMediaList(Integer caseId);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package uk.gov.hmcts.darts.common.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import uk.gov.hmcts.darts.common.entity.CourtCaseEntity;
import uk.gov.hmcts.darts.common.entity.MediaEntity;
Expand All @@ -12,10 +13,23 @@
@Repository
public interface MediaLinkedCaseRepository extends JpaRepository<MediaLinkedCaseEntity, Integer> {

List<MediaLinkedCaseEntity> findAllByCourtCase(CourtCaseEntity courtCase);

List<MediaLinkedCaseEntity> findByMedia(MediaEntity media);

List<MediaLinkedCaseEntity> findByMediaAndSource(MediaEntity mediaEntity, MediaLinkedCaseSourceType source);

boolean existsByMediaAndCourtCase(MediaEntity media, CourtCaseEntity courtCase);

@Query("""
SELECT COUNT(DISTINCT cc) = (COUNT(cc.isDataAnonymised) filter (where cc.isDataAnonymised = true))
FROM MediaLinkedCaseEntity mlc
LEFT JOIN CourtCaseEntity cc
ON (mlc.courtCase = cc
or (cc.courthouse.courthouseName = mlc.courthouseName and cc.caseNumber = mlc.caseNumber))
WHERE mlc.media = :mediaEntity
group by mlc.media
"""
)
boolean areAllAssociatedCasesAnonymised(MediaEntity mediaEntity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package uk.gov.hmcts.darts.hearings.api;

public interface HearingApi {

void removeMediaLinkToHearing(Integer courtCaseId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package uk.gov.hmcts.darts.hearings.api.impl;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import uk.gov.hmcts.darts.hearings.api.HearingApi;
import uk.gov.hmcts.darts.hearings.service.HearingsService;

@Service
@RequiredArgsConstructor
public class HearingApiImpl implements HearingApi {

private final HearingsService hearingsService;

@Override
public void removeMediaLinkToHearing(Integer courtCaseId) {
hearingsService.removeMediaLinkToHearing(courtCaseId);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public interface HearingsService {
List<Transcript> getTranscriptsByHearingId(Integer hearingId);

List<Annotation> getAnnotationsByHearingId(Integer hearingId);

void removeMediaLinkToHearing(Integer courtCaseId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import uk.gov.hmcts.darts.common.repository.AnnotationRepository;
import uk.gov.hmcts.darts.common.repository.EventRepository;
import uk.gov.hmcts.darts.common.repository.HearingRepository;
import uk.gov.hmcts.darts.common.repository.MediaLinkedCaseRepository;
import uk.gov.hmcts.darts.common.repository.TranscriptionRepository;
import uk.gov.hmcts.darts.hearings.exception.HearingApiError;
import uk.gov.hmcts.darts.hearings.mapper.GetAnnotationsResponseMapper;
Expand All @@ -37,6 +38,7 @@ public class HearingsServiceImpl implements HearingsService {

private final GetHearingResponseMapper getHearingResponseMapper;
private final HearingRepository hearingRepository;
private final MediaLinkedCaseRepository mediaLinkedCaseRepository;
private final TranscriptionRepository transcriptionRepository;
private final EventRepository eventRepository;
private final AnnotationRepository annotationRepository;
Expand Down Expand Up @@ -98,6 +100,25 @@ public List<Annotation> getAnnotationsByHearingId(Integer hearingId) {
return GetAnnotationsResponseMapper.mapToAnnotations(annotations, hearingId);
}

@Override
public void removeMediaLinkToHearing(Integer courtCaseId) {
hearingRepository.findByCaseIdWithMediaList(courtCaseId).ifPresent(hearing ->
hearing.getMediaList().forEach(media -> {
// Check all cases linked to a hearing have been expired/anonymised
if (!mediaLinkedCaseRepository.areAllAssociatedCasesAnonymised(media)) {
log.info("Media {} link not removed for case id {} as not all associated cases are expired", media.getId(), courtCaseId);
} else {
hearingRepository.findHearingIdsByMediaId(media.getId()).forEach(hearingForMedia -> {
hearingForMedia.setMediaList(null);
hearingRepository.save(hearingForMedia);
log.info("Media id {} link removed for hearing id {} on the expiry of case id {}",
media.getId(), hearingForMedia.getId(), courtCaseId);
});
}
})
);
}

private void validateCaseIsNotExpiredFromHearingId(Integer hearingId) {
Optional<HearingEntity> hearingEntity = hearingRepository.findById(hearingId);
hearingEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import uk.gov.hmcts.darts.common.repository.AutomatedTaskRepository;
import uk.gov.hmcts.darts.common.repository.CaseRepository;
import uk.gov.hmcts.darts.common.service.DataAnonymisationService;
import uk.gov.hmcts.darts.hearings.api.HearingApi;
import uk.gov.hmcts.darts.log.api.LogApi;
import uk.gov.hmcts.darts.task.api.AutomatedTaskName;
import uk.gov.hmcts.darts.task.config.CaseExpiryDeletionAutomatedTaskConfig;
Expand All @@ -30,6 +31,7 @@ public class CaseExpiryDeletionAutomatedTask

private final CurrentTimeHelper currentTimeHelper;
private final DataAnonymisationService dataAnonymisationService;
private final HearingApi hearingApi;
private final CaseRepository caseRepository;
private final UserIdentity userAccountService;

Expand All @@ -38,11 +40,14 @@ public CaseExpiryDeletionAutomatedTask(AutomatedTaskRepository automatedTaskRepo
CurrentTimeHelper currentTimeHelper,
CaseRepository caseRepository,
LogApi logApi, LockService lockService,
DataAnonymisationService dataAnonymisationService, UserIdentity userAccountService) {
DataAnonymisationService dataAnonymisationService,
HearingApi hearingApi,
UserIdentity userAccountService) {
super(automatedTaskRepository, automatedTaskConfigurationProperties, logApi, lockService);
this.currentTimeHelper = currentTimeHelper;
this.caseRepository = caseRepository;
this.dataAnonymisationService = dataAnonymisationService;
this.hearingApi = hearingApi;
this.userAccountService = userAccountService;
}

Expand All @@ -62,6 +67,7 @@ public void runTask() {
try {
log.info("Anonymising case with id: {} because the criteria for retention has been met.", courtCaseId);
dataAnonymisationService.anonymiseCourtCaseById(userAccount, courtCaseId, false);
hearingApi.removeMediaLinkToHearing(courtCaseId);
} catch (Exception e) {
log.error("An error occurred while anonymising case with id: {}", courtCaseId, e);
}
Expand Down
Loading
Loading