diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentationUnitController.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentationUnitController.java index 27845fcca..0a87e214d 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentationUnitController.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentationUnitController.java @@ -59,6 +59,7 @@ @Slf4j public class DocumentationUnitController { private final DocumentationUnitService service; + private final DuplicateCheckService duplicateCheckService; private final UserService userService; private final AttachmentService attachmentService; private final ConverterService converterService; @@ -70,6 +71,7 @@ public class DocumentationUnitController { public DocumentationUnitController( DocumentationUnitService service, + DuplicateCheckService duplicateCheckService, UserService userService, AttachmentService attachmentService, ConverterService converterService, @@ -79,6 +81,7 @@ public DocumentationUnitController( DocumentationUnitDocxMetadataInitializationService documentationUnitDocxMetadataInitializationService) { this.service = service; + this.duplicateCheckService = duplicateCheckService; this.userService = userService; this.attachmentService = attachmentService; this.converterService = converterService; @@ -240,6 +243,14 @@ public ResponseEntity getByDocumentNumber( } } + @GetMapping(value = "/{documentNumber}/duplicates", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("@userHasReadAccessByDocumentNumber.apply(#documentNumber)") + public ResponseEntity> getDuplicatesByDocumentNumber( + @AuthenticationPrincipal OidcUser oidcUser, @NonNull @PathVariable String documentNumber) { + var documentationUnits = duplicateCheckService.getDuplicates(documentNumber); + return ResponseEntity.ok(documentationUnits); + } + @DeleteMapping(value = "/{uuid}") @PreAuthorize("@userIsInternal.apply(#oidcUser) and @userHasWriteAccess.apply(#uuid)") public ResponseEntity deleteByUuid( diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DuplicateCheckService.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DuplicateCheckService.java new file mode 100644 index 000000000..d67028ba5 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DuplicateCheckService.java @@ -0,0 +1,70 @@ +package de.bund.digitalservice.ris.caselaw.adapter; + +import de.bund.digitalservice.ris.caselaw.domain.DocumentationUnit; +import de.bund.digitalservice.ris.caselaw.domain.DocumentationUnitService; +import de.bund.digitalservice.ris.caselaw.domain.DuplicateCheckRepository; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class DuplicateCheckService { + private final DocumentationUnitService documentationUnitService; + + private final DuplicateCheckRepository repository; + + public DuplicateCheckService( + DuplicateCheckRepository repository, DocumentationUnitService documentationUnitService) { + this.repository = repository; + this.documentationUnitService = documentationUnitService; + } + + List getDuplicates(String docNumber) { + try { + var documentationUnit = documentationUnitService.getByDocumentNumber(docNumber); + + var fileNumbers = documentationUnit.coreData().fileNumbers(); + var deviatingFileNumbers = documentationUnit.coreData().deviatingFileNumbers(); + List allFileNumbers = new ArrayList<>(); + if (fileNumbers != null) { + allFileNumbers.addAll(fileNumbers.stream().map(String::toUpperCase).toList()); + } + if (deviatingFileNumbers != null) { + allFileNumbers.addAll(deviatingFileNumbers.stream().map(String::toUpperCase).toList()); + } + + var decisionDate = documentationUnit.coreData().decisionDate(); + var deviatingDecisionDates = documentationUnit.coreData().deviatingDecisionDates(); + List allDates = new ArrayList<>(); + if (decisionDate != null) allDates.add(decisionDate); + if (!deviatingDecisionDates.isEmpty()) allDates.addAll(deviatingDecisionDates); + + List allCourtIds = new ArrayList<>(); + var court = documentationUnit.coreData().court(); + if (court != null) allCourtIds.add(court.id()); + + List allDeviatingCourts = new ArrayList<>(); + var deviatingCourts = documentationUnit.coreData().deviatingCourts(); + if (deviatingCourts != null) + allDeviatingCourts.addAll(deviatingCourts.stream().map(String::toUpperCase).toList()); + + var ecli = documentationUnit.coreData().ecli(); + var deviatingEclis = documentationUnit.coreData().deviatingEclis(); + List allEclis = new ArrayList<>(); + if (ecli != null) allEclis.add(ecli.toUpperCase()); + if (deviatingEclis != null) + allEclis.addAll(deviatingEclis.stream().map(String::toUpperCase).toList()); + + var documentType = documentationUnit.coreData().documentType(); + return repository.findDuplicates( + allFileNumbers, allDates, allCourtIds, allDeviatingCourts, allEclis, documentType.uuid()); + } catch (Exception e) { + log.debug(e.getMessage()); + return List.of(); + } + } +} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDuplicateCheckRepository.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDuplicateCheckRepository.java new file mode 100644 index 000000000..2bff8b1b5 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDuplicateCheckRepository.java @@ -0,0 +1,170 @@ +package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface DatabaseDuplicateCheckRepository + extends JpaRepository { + + @Query( + nativeQuery = true, + value = + """ + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.file_number fileNumber + ON documentationUnit.id = fileNumber.documentation_unit_id + WHERE upper(fileNumber.value) IN (:allFileNumbers) + AND documentationUnit.decision_date IN (:allDates) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.file_number fileNumber + ON documentationUnit.id = fileNumber.documentation_unit_id + JOIN incremental_migration.deviating_date deviatingDate + ON documentationUnit.id = deviatingDate.documentation_unit_id + WHERE upper(fileNumber.value) IN (:allFileNumbers) + AND deviatingDate.value IN (:allDates) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.deviating_file_number deviatingFileNumber + ON documentationUnit.id = deviatingFileNumber.documentation_unit_id + WHERE upper(deviatingFileNumber.value) IN (:allFileNumbers) + AND documentationUnit.decision_date IN (:allDates) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.deviating_file_number deviatingFileNumber + ON documentationUnit.id = deviatingFileNumber.documentation_unit_id + JOIN incremental_migration.deviating_date deviatingDate + ON documentationUnit.id = deviatingDate.documentation_unit_id + WHERE upper(deviatingFileNumber.value) IN (:allFileNumbers) + AND deviatingDate.value IN (:allDates) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.file_number fileNumber + ON documentationUnit.id = fileNumber.documentation_unit_id + JOIN incremental_migration.court court + ON documentationUnit.court_id = court.id + WHERE upper(fileNumber.value) IN (:allFileNumbers) + AND court.id IN (:allCourtIds) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.deviating_file_number deviatingFileNumber + ON documentationUnit.id = deviatingFileNumber.documentation_unit_id + JOIN incremental_migration.court court + ON documentationUnit.court_id = court.id + WHERE upper(deviatingFileNumber.value) IN (:allFileNumbers) + AND court.id IN (:allCourtIds) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.file_number fileNumber + ON documentationUnit.id = fileNumber.documentation_unit_id + JOIN incremental_migration.deviating_court deviatingCourt + ON documentationUnit.id = deviatingCourt.documentation_unit_id + WHERE upper(fileNumber.value) IN (:allFileNumbers) + AND upper(deviatingCourt.value) IN (:allDeviatingCourts) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.deviating_file_number deviatingFileNumber + ON documentationUnit.id = deviatingFileNumber.documentation_unit_id + JOIN incremental_migration.deviating_court deviatingCourt + ON documentationUnit.id = deviatingCourt.documentation_unit_id + WHERE upper(deviatingFileNumber.value) IN (:allFileNumbers) + AND upper(deviatingCourt.value) IN (:allDeviatingCourts) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.file_number fileNumber + ON documentationUnit.id = fileNumber.documentation_unit_id + WHERE upper(fileNumber.value) IN (:allFileNumbers) + AND upper(documentationUnit.ecli) IN (:allEclis) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.deviating_file_number deviatingFileNumber + ON documentationUnit.id = deviatingFileNumber.documentation_unit_id + WHERE upper(deviatingFileNumber.value) IN (:allFileNumbers) + AND upper(documentationUnit.ecli) IN (:allEclis) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.file_number fileNumber + ON documentationUnit.id = fileNumber.documentation_unit_id + JOIN incremental_migration.deviating_ecli deviatingEcli + ON documentationUnit.id = deviatingEcli.documentation_unit_id + WHERE upper(fileNumber.value) IN (:allFileNumbers) + AND upper(deviatingEcli.value) IN (:allEclis) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.deviating_file_number deviatingFileNumber + ON documentationUnit.id = deviatingFileNumber.documentation_unit_id + JOIN incremental_migration.deviating_ecli deviatingEcli + ON documentationUnit.id = deviatingEcli.documentation_unit_id + WHERE upper(deviatingFileNumber.value) IN (:allFileNumbers) + AND upper(deviatingEcli.value) IN (:allEclis) + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.file_number fileNumber + ON documentationUnit.id = fileNumber.documentation_unit_id + JOIN incremental_migration.document_type documentType + ON documentationUnit.document_type_id = documentType.id + WHERE upper(fileNumber.value) IN (:allFileNumbers) + AND documentType.id = :documentTypeId + + UNION + + SELECT documentationUnit.* + FROM incremental_migration.documentation_unit documentationUnit + JOIN incremental_migration.deviating_file_number deviatingFileNumber + ON documentationUnit.id = deviatingFileNumber.documentation_unit_id + JOIN incremental_migration.document_type documentType + ON documentationUnit.document_type_id = documentType.id + WHERE upper(deviatingFileNumber.value) IN (:allFileNumbers) + AND documentType.id = :documentTypeId +""") + List findDuplicates( + @Param("allFileNumbers") List allFileNumbers, + @Param("allDates") List allDates, + @Param("allCourtIds") List allCourtIds, + @Param("allDeviatingCourts") List allDeviatingCourts, + @Param("allEclis") List allEclis, + @Param("documentTypeId") UUID documentTypeId); +} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresDuplicateCheckRepositoryImpl.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresDuplicateCheckRepositoryImpl.java new file mode 100644 index 000000000..b06af6e93 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresDuplicateCheckRepositoryImpl.java @@ -0,0 +1,49 @@ +package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; + +import de.bund.digitalservice.ris.caselaw.adapter.transformer.DocumentationUnitTransformer; +import de.bund.digitalservice.ris.caselaw.domain.DocumentationUnit; +import de.bund.digitalservice.ris.caselaw.domain.DuplicateCheckRepository; +import java.time.LocalDate; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Repository +public class PostgresDuplicateCheckRepositoryImpl implements DuplicateCheckRepository { + + private final DatabaseDuplicateCheckRepository repository; + + public PostgresDuplicateCheckRepositoryImpl(DatabaseDuplicateCheckRepository repository) { + this.repository = repository; + } + + @Override + @Transactional + public List findDuplicates( + List allFileNumbers, + List allDates, + List allCourtIds, + List allDeviatingCourts, + List allEclis, + UUID documentTypeId) { + long startTime = System.currentTimeMillis(); + + List result = + repository.findDuplicates( + allFileNumbers, allDates, allCourtIds, allDeviatingCourts, allEclis, documentTypeId); + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + log.info("Query executed in: " + duration + " ms"); + + return result.stream() + .filter(Objects::nonNull) + .distinct() + .map(DocumentationUnitTransformer::transformToDomain) + .toList(); + } +} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DuplicateCheckRepository.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DuplicateCheckRepository.java new file mode 100644 index 000000000..6b5f8f594 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DuplicateCheckRepository.java @@ -0,0 +1,18 @@ +package de.bund.digitalservice.ris.caselaw.domain; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface DuplicateCheckRepository { + + List findDuplicates( + List allFileNumbers, + List allDates, + List allCourtIds, + List allDeviatingCourts, + List allEclis, + UUID documentTypeId); +}