diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index a632809b7f..d0f9575780 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -198,7 +198,7 @@ dependencies { // implementation(files("../../neuris-juris-xml-export/build/libs/neuris-juris-xml-export-0.10.19.jar")) // or with local gradle project (look also into settings.gradle.kts) // implementation(project(":exporter")) - + implementation("de.bund.digitalservice:neuris-caselaw-migration-schema:0.0.29") // for local development: // implementation(files("../../ris-data-migration/schema/build/libs/schema-0.0.28.jar")) diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentTypeController.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentTypeController.java index 42e39d7f41..ab9cc9a0ea 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentTypeController.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentTypeController.java @@ -28,4 +28,11 @@ public List getDocumentTypes( @RequestParam(value = "q") Optional searchStr) { return service.getDocumentTypes(searchStr); } + + @GetMapping(value = "/dependent-literature", produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("isAuthenticated()") + public List getDependentLiteratureDocumentTypes( + @RequestParam(value = "q") Optional searchStr) { + return service.getDependentLiteratureDocumentTypes(searchStr); + } } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseReferenceRepository.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseReferenceRepository.java new file mode 100644 index 0000000000..75f7ea0631 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseReferenceRepository.java @@ -0,0 +1,8 @@ +package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface DatabaseReferenceRepository extends JpaRepository {} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationDTO.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationDTO.java new file mode 100644 index 0000000000..0ceb01b106 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationDTO.java @@ -0,0 +1,58 @@ +package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; + +import de.bund.digitalservice.ris.caselaw.domain.DependentLiteratureCitationType; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +@Entity +@Table(schema = "incremental_migration", name = "dependent_literature_citation") +public class DependentLiteratureCitationDTO { + @Id private UUID id; + + private String author; + + @NotBlank private String citation; + + @JoinColumn(name = "document_type_id") + @ManyToOne + private DocumentTypeDTO documentType; + + @Column(name = "legal_periodical_raw_value") + @NotNull + private String legalPeriodicalRawValue; + + @JoinColumn(name = "legal_periodical_id") + @ManyToOne + private LegalPeriodicalDTO legalPeriodical; + + @Column(name = "dtype") + @Convert(converter = DependentLiteratureCitationTypeConverter.class) + private DependentLiteratureCitationType type; + + @ManyToOne + @JoinColumn(name = "documentation_unit_id") + private DocumentationUnitDTO documentationUnit; + + @Column(name = "document_type_raw_value") + private String documentTypeRawValue; + + private Integer rank; +} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationRepository.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationRepository.java new file mode 100644 index 0000000000..24f9d14b82 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationRepository.java @@ -0,0 +1,9 @@ +package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; + +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface DependentLiteratureCitationRepository + extends JpaRepository {} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationTypeConverter.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationTypeConverter.java new file mode 100644 index 0000000000..dea8a678fa --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DependentLiteratureCitationTypeConverter.java @@ -0,0 +1,20 @@ +package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; + +import de.bund.digitalservice.ris.caselaw.domain.DependentLiteratureCitationType; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter(autoApply = true) +public class DependentLiteratureCitationTypeConverter + implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(DependentLiteratureCitationType attribute) { + return (attribute == null) ? null : attribute.getValue(); // Store "passive" or "active" + } + + @Override + public DependentLiteratureCitationType convertToEntityAttribute(String dbData) { + return (dbData == null) ? null : DependentLiteratureCitationType.of(dbData); + } +} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentationUnitDTO.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentationUnitDTO.java index b104b3e846..5a8f1fc5a0 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentationUnitDTO.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentationUnitDTO.java @@ -292,6 +292,11 @@ public class DocumentationUnitDTO implements DocumentationUnitListItemDTO { @OrderBy("rank") private List references = new ArrayList<>(); + @OneToMany(mappedBy = "documentationUnit", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default + @OrderBy("rank") + private List dependentLiteratureCitations = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @JoinColumn(name = "documentation_unit_id", nullable = false) @OrderBy("rank") diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/EditionReferenceDTO.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/EditionReferenceDTO.java index 79eba03155..a161a7474e 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/EditionReferenceDTO.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/EditionReferenceDTO.java @@ -1,6 +1,5 @@ package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -35,11 +34,6 @@ public class EditionReferenceDTO { @JoinColumn(name = "edition_id", nullable = false) private LegalPeriodicalEditionDTO edition; - // @ManyToOne - // @JoinColumn(name = "reference_id", insertable = false, updatable = false) - // private DependentLiteratureCitationDTO literatureCitation; - - @ManyToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "reference_id") - private ReferenceDTO reference; + @Column(name = "reference_id") + private UUID referenceId; } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/LegalPeriodicalEditionDTO.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/LegalPeriodicalEditionDTO.java index 234667221f..d284490091 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/LegalPeriodicalEditionDTO.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/LegalPeriodicalEditionDTO.java @@ -52,35 +52,34 @@ public class LegalPeriodicalEditionDTO { private List editionReferences = new ArrayList<>(); // Methods to get references and literature citations - // public List getLiteratureCitations() { - // return editionReferences.stream() - // .filter(ref -> "literature".equals(ref.getDtype())) - // .map(EditionReferenceDTO::getLiteratureCitation) - // .collect(Collectors.toList()); - // } + public List getLiteratureCitations() { + return editionReferences.stream() + .filter(ref -> "literature".equals(ref.getDtype())) + .map(EditionReferenceDTO::getReferenceId) + .collect(Collectors.toList()); + } - public List getReferences() { + public List getReferences() { return editionReferences.stream() .filter(ref -> "reference".equals(ref.getDtype())) - .map(EditionReferenceDTO::getReference) + .map(EditionReferenceDTO::getReferenceId) .collect(Collectors.toList()); } - // - // public void setLiteratureCitations(List literatureCitations) { - // // Remove existing literature citations - // editionReferences.removeIf(ref -> "literature".equals(ref.getDtype())); - // - // // Add new literature citations with updated rank - // for (DependentLiteratureCitationDTO citation : literatureCitations) { - // EditionReferenceDTO editionReference = new EditionReferenceDTO(); - // editionReference.setEdition(this); - // editionReference.setLiteratureCitation(citation); - // editionReference.setRank(citation.getRank()); - // editionReference.setDtype("literature"); - // editionReferences.add(editionReference); - // } - // } + public void setLiteratureCitations(List literatureCitations) { + // Remove existing literature citations + editionReferences.removeIf(ref -> "literature".equals(ref.getDtype())); + + // Add new literature citations with updated rank + for (DependentLiteratureCitationDTO citation : literatureCitations) { + EditionReferenceDTO editionReference = new EditionReferenceDTO(); + editionReference.setEdition(this); + editionReference.setReferenceId(citation.getId()); + editionReference.setRank(citation.getRank()); + editionReference.setDtype("literature"); + editionReferences.add(editionReference); + } + } public void setReferences(List references) { // Remove existing references @@ -89,7 +88,7 @@ public void setReferences(List references) { // Add new references with updated rank for (ReferenceDTO reference : references) { EditionReferenceDTO editionReference = new EditionReferenceDTO(); - editionReference.setReference(reference); + editionReference.setReferenceId(reference.getId()); editionReference.setRank(reference.getRank()); editionReference.setDtype("reference"); editionReference.setEdition(this); diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresDocumentTypeRepositoryImpl.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresDocumentTypeRepositoryImpl.java index 8a45251e3b..fd295e2c97 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresDocumentTypeRepositoryImpl.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresDocumentTypeRepositoryImpl.java @@ -30,6 +30,25 @@ public List findCaselawBySearchStr(String searchString) { .toList(); } + @Override + public List findDependentLiteratureBySearchStr(String searchString) { + return repository + .findCaselawBySearchStrAndCategory( + searchString, categoryRepository.findFirstByLabel("U").getId()) + .stream() + .map(DocumentTypeTransformer::transformToDomain) + .toList(); + } + + @Override + public List findAllDependentLiteratureOrderByAbbreviationAscLabelAsc() { + return repository + .findAllByCategoryOrderByAbbreviationAscLabelAsc(categoryRepository.findFirstByLabel("U")) + .stream() + .map(DocumentTypeTransformer::transformToDomain) + .toList(); + } + @Override public Optional findUniqueCaselawBySearchStr(String searchString) { return repository diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresLegalPeriodicalEditionRepositoryImpl.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresLegalPeriodicalEditionRepositoryImpl.java index 481b361e0c..e59b5a3a17 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresLegalPeriodicalEditionRepositoryImpl.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/PostgresLegalPeriodicalEditionRepositoryImpl.java @@ -1,10 +1,12 @@ package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; +import de.bund.digitalservice.ris.caselaw.adapter.transformer.DependentLiteratureTransformer; import de.bund.digitalservice.ris.caselaw.adapter.transformer.LegalPeriodicalEditionTransformer; import de.bund.digitalservice.ris.caselaw.adapter.transformer.ReferenceTransformer; import de.bund.digitalservice.ris.caselaw.domain.LegalPeriodicalEdition; import de.bund.digitalservice.ris.caselaw.domain.LegalPeriodicalEditionRepository; import de.bund.digitalservice.ris.caselaw.domain.Reference; +import de.bund.digitalservice.ris.caselaw.domain.ReferenceType; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -19,23 +21,39 @@ public class PostgresLegalPeriodicalEditionRepositoryImpl implements LegalPeriodicalEditionRepository { private final DatabaseLegalPeriodicalEditionRepository repository; private final DatabaseDocumentationUnitRepository documentationUnitRepository; + private final DatabaseReferenceRepository referenceRepository; + private final DependentLiteratureCitationRepository dependentLiteratureCitationRepository; public PostgresLegalPeriodicalEditionRepositoryImpl( DatabaseLegalPeriodicalEditionRepository repository, - DatabaseDocumentationUnitRepository documentationUnitRepository) { + DatabaseDocumentationUnitRepository documentationUnitRepository, + DatabaseReferenceRepository referenceRepository, + DependentLiteratureCitationRepository dependentLiteratureCitationRepository) { this.repository = repository; this.documentationUnitRepository = documentationUnitRepository; + this.referenceRepository = referenceRepository; + this.dependentLiteratureCitationRepository = dependentLiteratureCitationRepository; } @Transactional(transactionManager = "jpaTransactionManager") public Optional findById(UUID id) { - return repository.findById(id).map(LegalPeriodicalEditionTransformer::transformToDomain); + return repository + .findById(id) + .map( + edition -> + LegalPeriodicalEditionTransformer.transformToDomain(edition).toBuilder() + .references(addReferences(edition)) + .build()); } @Transactional(transactionManager = "jpaTransactionManager") public List findAllByLegalPeriodicalId(UUID legalPeriodicalId) { return repository.findAllByLegalPeriodicalIdOrderByCreatedAtDesc(legalPeriodicalId).stream() - .map(LegalPeriodicalEditionTransformer::transformToDomain) + .map( + edition -> + LegalPeriodicalEditionTransformer.transformToDomain(edition).toBuilder() + .references(addReferences(edition)) + .build()) .toList(); } @@ -43,12 +61,40 @@ public List findAllByLegalPeriodicalId(UUID legalPeriodi public LegalPeriodicalEdition save(LegalPeriodicalEdition legalPeriodicalEdition) { List referenceDTOS = createReferenceDTOs(legalPeriodicalEdition); + List dependentLiteratureCitationDTOS = + createLiteratureReferenceDTOs(legalPeriodicalEdition); deleteDocUnitLinksForDeletedReferences(legalPeriodicalEdition); - var edition = LegalPeriodicalEditionTransformer.transformToDTO(legalPeriodicalEdition); - edition.setReferences(referenceDTOS); // Add the new references + var editionDTO = LegalPeriodicalEditionTransformer.transformToDTO(legalPeriodicalEdition); + editionDTO.setReferences(referenceDTOS); // Add the new references + editionDTO.setLiteratureCitations( + dependentLiteratureCitationDTOS); // Add the new literature references - return LegalPeriodicalEditionTransformer.transformToDomain(repository.save(edition)); + return LegalPeriodicalEditionTransformer.transformToDomain(repository.save(editionDTO)) + .toBuilder() + .references(addReferences(editionDTO)) + .build(); + } + + private ArrayList addReferences(LegalPeriodicalEditionDTO editionDTO) { + ArrayList references = new ArrayList<>(); + + if (editionDTO.getReferences() != null) { + references.addAll( + editionDTO.getReferences().stream() + .map(id -> referenceRepository.findById(id).orElse(null)) + .map(ReferenceTransformer::transformToDomain) + .toList()); + } + + if (editionDTO.getLiteratureCitations() != null) { + references.addAll( + editionDTO.getLiteratureCitations().stream() + .map(id -> dependentLiteratureCitationRepository.findById(id).orElse(null)) + .map(DependentLiteratureTransformer::transformToDomain) + .toList()); + } + return references; } private void deleteDocUnitLinksForDeletedReferences(LegalPeriodicalEdition updatedEdition) { @@ -57,22 +103,27 @@ private void deleteDocUnitLinksForDeletedReferences(LegalPeriodicalEdition updat return; } // Ensure it's removed from DocumentationUnit's references - for (ReferenceDTO reference : oldEdition.get().getReferences()) { + for (UUID reference : oldEdition.get().getReferences()) { // skip all existing references if (updatedEdition.references().stream() - .anyMatch(newReference -> newReference.id().equals(reference.getId()))) { + .anyMatch(newReference -> newReference.id().equals(reference))) { + continue; + } + + var referenceDTO = referenceRepository.findById(reference); + if (referenceDTO.isEmpty()) { continue; } // delete all deleted references and possible source reference documentationUnitRepository - .findById(reference.getDocumentationUnit().getId()) + .findById(referenceDTO.get().getDocumentationUnit().getId()) .ifPresent( docUnit -> { - docUnit.getReferences().remove(reference); + docUnit.getReferences().remove(referenceDTO.get()); if (docUnit.getSource().stream() .findFirst() .map(SourceDTO::getReference) - .filter(ref -> ref.getId().equals(reference.getId())) + .filter(ref -> ref.getId().equals(reference)) .isPresent()) { docUnit.getSource().removeFirst(); } @@ -96,6 +147,9 @@ private List createReferenceDTOs(LegalPeriodicalEdition legalPerio continue; } + if (!reference.referenceType().equals(ReferenceType.CASELAW)) { + continue; + } var newReference = ReferenceTransformer.transformToDTO(reference); newReference.setDocumentationUnit(docUnit.get()); @@ -113,11 +167,51 @@ private List createReferenceDTOs(LegalPeriodicalEdition legalPerio .orElse(0) + 1)); - referenceDTOS.add(newReference); + referenceDTOS.add(referenceRepository.save(newReference)); } + return referenceDTOS; } + private List createLiteratureReferenceDTOs( + LegalPeriodicalEdition legalPeriodicalEdition) { + List dependentLiteratureCitationDTOS = new ArrayList<>(); + if (legalPeriodicalEdition.references() == null) { + return dependentLiteratureCitationDTOS; + } + for (Reference reference : legalPeriodicalEdition.references()) { + var docUnit = + documentationUnitRepository.findByDocumentNumber( + reference.documentationUnit().getDocumentNumber()); + if (docUnit.isEmpty()) { + // don't add references to non-existing documentation units + continue; + } + + if (!reference.referenceType().equals(ReferenceType.LITERATURE)) { + continue; + } + + var newReference = DependentLiteratureTransformer.transformToDTO(reference); + newReference.setDocumentationUnit(docUnit.get()); + // keep rank for existing references and set to max rank +1 for new references + newReference.setRank( + docUnit.get().getReferences().stream() + .filter(referenceDTO -> referenceDTO.getId().equals(reference.id())) + .findFirst() + .map(ReferenceDTO::getRank) + .orElseGet( + () -> + docUnit.get().getDependentLiteratureCitations().stream() + .map(DependentLiteratureCitationDTO::getRank) + .max(Comparator.naturalOrder()) + .orElse(0) + + 1)); + dependentLiteratureCitationDTOS.add(dependentLiteratureCitationRepository.save(newReference)); + } + return dependentLiteratureCitationDTOS; + } + @Override public void delete(LegalPeriodicalEdition legalPeriodicalEdition) { repository.deleteById(legalPeriodicalEdition.id()); diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/ReferenceDTO.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/ReferenceDTO.java index 1f9e9588e1..80e41bb29f 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/ReferenceDTO.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/ReferenceDTO.java @@ -54,6 +54,11 @@ public class ReferenceDTO { @NotNull private String legalPeriodicalRawValue; - @OneToMany(mappedBy = "reference", cascade = CascadeType.REMOVE) + @OneToMany(cascade = CascadeType.REMOVE) + @JoinColumn( + name = "reference_id", + referencedColumnName = "id", + insertable = false, + updatable = false) private List editionReferences; } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DependentLiteratureTransformer.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DependentLiteratureTransformer.java new file mode 100644 index 0000000000..40063d7e83 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DependentLiteratureTransformer.java @@ -0,0 +1,76 @@ +package de.bund.digitalservice.ris.caselaw.adapter.transformer; + +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DependentLiteratureCitationDTO; +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentTypeDTO; +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentationUnitDTO; +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.LegalPeriodicalDTO; +import de.bund.digitalservice.ris.caselaw.domain.DependentLiteratureCitationType; +import de.bund.digitalservice.ris.caselaw.domain.Reference; +import de.bund.digitalservice.ris.caselaw.domain.ReferenceType; +import de.bund.digitalservice.ris.caselaw.domain.lookuptable.LegalPeriodical; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class DependentLiteratureTransformer { + public static Reference transformToDomain(DependentLiteratureCitationDTO literatureCitationDTO) { + if (literatureCitationDTO == null) { + return null; + } + LegalPeriodical legalPeriodical = null; + + if (literatureCitationDTO.getLegalPeriodical() != null) { + legalPeriodical = + LegalPeriodicalTransformer.transformToDomain(literatureCitationDTO.getLegalPeriodical()); + } + + return Reference.builder() + .id(literatureCitationDTO.getId()) + .author(literatureCitationDTO.getAuthor()) + .documentType( + DocumentTypeTransformer.transformToDomain(literatureCitationDTO.getDocumentType())) + .legalPeriodical(legalPeriodical) + .legalPeriodicalRawValue(literatureCitationDTO.getLegalPeriodicalRawValue()) + .citation(literatureCitationDTO.getCitation()) + .referenceType(ReferenceType.LITERATURE) + .documentationUnit( + RelatedDocumentationUnitTransformer.transformToDomain( + literatureCitationDTO.getDocumentationUnit())) + .build(); + } + + public static DependentLiteratureCitationDTO transformToDTO(Reference reference) { + LegalPeriodicalDTO legalPeriodicalDTO = null; + String legalPeriodicalRawValue = null; + + if (reference.legalPeriodical() != null) { + legalPeriodicalDTO = LegalPeriodicalTransformer.transformToDTO(reference.legalPeriodical()); + legalPeriodicalRawValue = reference.legalPeriodical().abbreviation(); + } + + DocumentationUnitDTO documentationUnitDTO = null; + if (reference.documentationUnit() != null) { + documentationUnitDTO = + DocumentationUnitDTO.builder().id(reference.documentationUnit().getUuid()).build(); + } + + DocumentTypeDTO documentType = null; + if (reference.documentType() != null) { + documentType = DocumentTypeTransformer.transformToDTO(reference.documentType()); + } + + return DependentLiteratureCitationDTO.builder() + .id(reference.id()) + .author(reference.author()) + .citation(reference.citation()) + .documentType(documentType) + .legalPeriodicalRawValue( + legalPeriodicalRawValue != null + ? legalPeriodicalRawValue + : reference.legalPeriodicalRawValue()) + .legalPeriodical(legalPeriodicalDTO) + .type(DependentLiteratureCitationType.PASSIVE) + .documentTypeRawValue(documentType.getAbbreviation()) + .documentationUnit(documentationUnitDTO) + .build(); + } +} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentTypeTransformer.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentTypeTransformer.java index cf676a439a..4fc29870e5 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentTypeTransformer.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentTypeTransformer.java @@ -21,6 +21,10 @@ public static DocumentType transformToDomain(DocumentTypeDTO documentTypeDTO) { public static DocumentTypeDTO transformToDTO(DocumentType documentType) { return documentType == null ? null - : DocumentTypeDTO.builder().id(documentType.uuid()).label(documentType.label()).build(); + : DocumentTypeDTO.builder() + .id(documentType.uuid()) + .label(documentType.label()) + .abbreviation(documentType.jurisShortcut()) + .build(); } } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentationUnitTransformer.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentationUnitTransformer.java index f7feb90d52..37891b6636 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentationUnitTransformer.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/DocumentationUnitTransformer.java @@ -29,6 +29,8 @@ import de.bund.digitalservice.ris.caselaw.domain.ManagementData; import de.bund.digitalservice.ris.caselaw.domain.NormReference; import de.bund.digitalservice.ris.caselaw.domain.PreviousDecision; +import de.bund.digitalservice.ris.caselaw.domain.Reference; +import de.bund.digitalservice.ris.caselaw.domain.ReferenceType; import de.bund.digitalservice.ris.caselaw.domain.ShortTexts; import de.bund.digitalservice.ris.caselaw.domain.SingleNorm; import de.bund.digitalservice.ris.caselaw.domain.StringUtils; @@ -152,6 +154,7 @@ public static DocumentationUnitDTO transformToDTO( } addReferences(updatedDomainObject, builder); + addDependentLiteratureCitations(updatedDomainObject, builder); return builder.build(); } @@ -163,6 +166,7 @@ private static void addReferences( updatedDomainObject.references() == null ? Collections.emptyList() : updatedDomainObject.references().stream() + .filter(reference -> reference.referenceType() == ReferenceType.CASELAW) .map(ReferenceTransformer::transformToDTO) .map( referenceDTO -> { @@ -174,6 +178,24 @@ private static void addReferences( .toList()); } + private static void addDependentLiteratureCitations( + DocumentationUnit updatedDomainObject, DocumentationUnitDTOBuilder builder) { + AtomicInteger i = new AtomicInteger(1); + builder.dependentLiteratureCitations( + updatedDomainObject.references() == null + ? Collections.emptyList() + : updatedDomainObject.references().stream() + .filter(reference -> reference.referenceType() == ReferenceType.LITERATURE) + .map(DependentLiteratureTransformer::transformToDTO) + .map( + referenceDTO -> { + referenceDTO.setDocumentationUnit(builder.build()); + referenceDTO.setRank(i.getAndIncrement()); + return referenceDTO; + }) + .toList()); + } + private static void addTexts( DocumentationUnit updatedDomainObject, DocumentationUnitDTOBuilder builder) { ShortTexts shortTexts = updatedDomainObject.shortTexts(); @@ -744,12 +766,24 @@ public static DocumentationUnit transformToDomain(DocumentationUnitDTO documenta private static void addReferencesToDomain( DocumentationUnitDTO documentationUnitDTO, DocumentationUnit.DocumentationUnitBuilder builder) { - builder.references( - documentationUnitDTO.getReferences() == null - ? Collections.emptyList() - : documentationUnitDTO.getReferences().stream() - .map(ReferenceTransformer::transformToDomain) - .toList()); + + List references = new ArrayList<>(); + + if (documentationUnitDTO.getReferences() != null) { + references.addAll( + documentationUnitDTO.getReferences().stream() + .map(ReferenceTransformer::transformToDomain) + .toList()); + } + + if (documentationUnitDTO.getDependentLiteratureCitations() != null) { + references.addAll( + documentationUnitDTO.getDependentLiteratureCitations().stream() + .map(DependentLiteratureTransformer::transformToDomain) + .toList()); + } + + builder.references(references); } /** diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/LegalPeriodicalEditionTransformer.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/LegalPeriodicalEditionTransformer.java index dbc6c85951..d66b9a3ce5 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/LegalPeriodicalEditionTransformer.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/LegalPeriodicalEditionTransformer.java @@ -25,10 +25,6 @@ public static LegalPeriodicalEdition transformToDomain( .name(legalPeriodicalEditionDTO.getName()) .prefix(legalPeriodicalEditionDTO.getPrefix()) .suffix(legalPeriodicalEditionDTO.getSuffix()) - .references( - legalPeriodicalEditionDTO.getReferences().stream() - .map(ReferenceTransformer::transformToDomain) - .toList()) .build(); } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferenceTransformer.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferenceTransformer.java index 8dcf0cf071..bb4b36ef49 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferenceTransformer.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferenceTransformer.java @@ -4,16 +4,17 @@ import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.LegalPeriodicalDTO; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.ReferenceDTO; import de.bund.digitalservice.ris.caselaw.domain.Reference; +import de.bund.digitalservice.ris.caselaw.domain.ReferenceType; import de.bund.digitalservice.ris.caselaw.domain.lookuptable.LegalPeriodical; import lombok.experimental.UtilityClass; @UtilityClass public class ReferenceTransformer { public static Reference transformToDomain(ReferenceDTO referenceDTO) { - LegalPeriodical legalPeriodical = null; if (referenceDTO == null) { return null; } + LegalPeriodical legalPeriodical = null; if (referenceDTO.getLegalPeriodical() != null) { legalPeriodical = @@ -27,6 +28,7 @@ public static Reference transformToDomain(ReferenceDTO referenceDTO) { .legalPeriodicalRawValue(referenceDTO.getLegalPeriodicalRawValue()) .citation(referenceDTO.getCitation()) .footnote(referenceDTO.getFootnote()) + .referenceType(ReferenceType.CASELAW) .documentationUnit( RelatedDocumentationUnitTransformer.transformToDomain( referenceDTO.getDocumentationUnit())) diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DependentLiteratureCitationType.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DependentLiteratureCitationType.java new file mode 100644 index 0000000000..93d7ee8a36 --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DependentLiteratureCitationType.java @@ -0,0 +1,31 @@ +package de.bund.digitalservice.ris.caselaw.domain; + +public enum DependentLiteratureCitationType { + PASSIVE("passive"), + ACTIVE("active"); + + private final String value; + + DependentLiteratureCitationType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + // Method to get enum from lowercase string + public static DependentLiteratureCitationType of(String value) { + for (DependentLiteratureCitationType type : values()) { + if (type.getValue().equalsIgnoreCase(value)) { + return type; + } + } + throw new IllegalArgumentException("Invalid value: " + value); + } + + @Override + public String toString() { + return this.value; + } +} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeRepository.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeRepository.java index a5e7bc69e1..04c6715411 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeRepository.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeRepository.java @@ -9,6 +9,10 @@ public interface DocumentTypeRepository { List findCaselawBySearchStr(String searchString); + List findDependentLiteratureBySearchStr(String searchString); + + List findAllDependentLiteratureOrderByAbbreviationAscLabelAsc(); + Optional findUniqueCaselawBySearchStr(String searchString); List findAllByDocumentTypeOrderByAbbreviationAscLabelAsc(char shortcut); diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeService.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeService.java index 9408354ad1..521c8ec6f1 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeService.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/DocumentTypeService.java @@ -22,4 +22,12 @@ public List getDocumentTypes(Optional searchStr) { return documentTypeRepository.findAllByDocumentTypeOrderByAbbreviationAscLabelAsc('R'); } + + public List getDependentLiteratureDocumentTypes(Optional searchStr) { + if (searchStr.isPresent() && !searchStr.get().isBlank()) { + return documentTypeRepository.findDependentLiteratureBySearchStr(searchStr.get().trim()); + } + + return documentTypeRepository.findAllDependentLiteratureOrderByAbbreviationAscLabelAsc(); + } } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/Reference.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/Reference.java index 5215fbd79d..b779e0e206 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/Reference.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/Reference.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import de.bund.digitalservice.ris.caselaw.domain.lookuptable.LegalPeriodical; +import de.bund.digitalservice.ris.caselaw.domain.lookuptable.documenttype.DocumentType; import java.util.UUID; import lombok.Builder; @@ -23,4 +24,7 @@ public record Reference( String footnote, LegalPeriodical legalPeriodical, String legalPeriodicalRawValue, + DocumentType documentType, + String author, + ReferenceType referenceType, RelatedDocumentationUnit documentationUnit) {} diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/ReferenceType.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/ReferenceType.java new file mode 100644 index 0000000000..4748f2ab3c --- /dev/null +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/domain/ReferenceType.java @@ -0,0 +1,40 @@ +package de.bund.digitalservice.ris.caselaw.domain; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.HashMap; +import java.util.Map; + +public enum ReferenceType { + CASELAW("caselaw"), + LITERATURE("literature"); + + private final String label; + + // A map to quickly resolve string labels to enum values + private static final Map LABEL_MAP = new HashMap<>(); + + static { + for (ReferenceType type : values()) { + LABEL_MAP.put(type.label, type); + } + } + + ReferenceType(String label) { + this.label = label; + } + + @JsonValue + public String getLabel() { + return label; + } + + @JsonCreator + public static ReferenceType fromString(String label) { + ReferenceType type = LABEL_MAP.get(label.toLowerCase()); + if (type == null) { + throw new IllegalArgumentException("Invalid reference type: " + label); + } + return type; + } +} diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentTypeControllerTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentTypeControllerTest.java new file mode 100644 index 0000000000..9f279f759b --- /dev/null +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/DocumentTypeControllerTest.java @@ -0,0 +1,128 @@ +package de.bund.digitalservice.ris.caselaw.adapter; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import de.bund.digitalservice.ris.caselaw.TestConfig; +import de.bund.digitalservice.ris.caselaw.config.SecurityConfig; +import de.bund.digitalservice.ris.caselaw.domain.DocumentTypeService; +import de.bund.digitalservice.ris.caselaw.domain.lookuptable.documenttype.DocumentType; +import de.bund.digitalservice.ris.caselaw.webtestclient.RisWebTestClient; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(controllers = DocumentTypeController.class) +@Import({SecurityConfig.class, TestConfig.class}) +class DocumentTypeControllerTest { + + @Autowired private RisWebTestClient risWebTestClient; + + @MockBean private DocumentTypeService service; + @MockBean private ClientRegistrationRepository clientRegistrationRepository; + + private DocumentType documentType1; + private DocumentType documentType2; + + @BeforeEach + void setUp() { + documentType1 = + DocumentType.builder() + .uuid(UUID.randomUUID()) + .label("label1") + .jurisShortcut("abbreviation1") + .build(); + documentType2 = + DocumentType.builder() + .uuid(UUID.randomUUID()) + .label("label2") + .jurisShortcut("abbreviation2") + .build(); + } + + @Test + void shouldReturnListOfDocumentTypes_whenGetDocumentTypesIsCalled() throws Exception { + List documentTypes = List.of(documentType1, documentType2); + + when(service.getDocumentTypes(any(Optional.class))).thenReturn(documentTypes); + + risWebTestClient + .withDefaultLogin() + .get() + .uri("/api/v1/caselaw/documenttypes") + .exchange() + .expectStatus() + .isOk(); + + verify(service, times(1)).getDocumentTypes(any(Optional.class)); + } + + @Test + void + shouldReturnListOfDependentLiteratureDocumentTypes_whenGetDependentLiteratureDocumentTypesIsCalled() { + List documentTypes = List.of(documentType1, documentType2); + + when(service.getDependentLiteratureDocumentTypes(any(Optional.class))) + .thenReturn(documentTypes); + + risWebTestClient + .withDefaultLogin() + .get() + .uri("/api/v1/caselaw/documenttypes/dependent-literature") + .exchange() + .expectStatus() + .isOk(); + + verify(service, times(1)).getDependentLiteratureDocumentTypes(any(Optional.class)); + } + + @Test + void shouldCallServiceWithSearchQueryParameter_whenGetDocumentTypesIsCalledWithSearchString() + throws Exception { + List documentTypes = List.of(documentType1, documentType2); + + when(service.getDocumentTypes(any(Optional.class))).thenReturn(documentTypes); + + risWebTestClient + .withDefaultLogin() + .get() + .uri("/api/v1/caselaw/documenttypes?q=label1") + .exchange() + .expectStatus() + .isOk(); + + verify(service, times(1)).getDocumentTypes(Optional.of("label1")); + } + + @Test + void + shouldCallServiceWithSearchQueryParameter_whenGetDependentLiteratureDocumentTypesIsCalledWithSearchString() + throws Exception { + List documentTypes = List.of(documentType1, documentType2); + + when(service.getDependentLiteratureDocumentTypes(any(Optional.class))) + .thenReturn(documentTypes); + + risWebTestClient + .withDefaultLogin() + .get() + .uri("/api/v1/caselaw/documenttypes/dependent-literature?q=label2") + .exchange() + .expectStatus() + .isOk(); + + verify(service, times(1)).getDependentLiteratureDocumentTypes(Optional.of("label2")); + } +} diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferencesTransformerTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferencesTransformerTest.java index 7903f8907b..cfa15f0ddc 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferencesTransformerTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/transformer/ReferencesTransformerTest.java @@ -12,6 +12,7 @@ import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.ReferenceDTO; import de.bund.digitalservice.ris.caselaw.domain.DocumentationUnit; import de.bund.digitalservice.ris.caselaw.domain.Reference; +import de.bund.digitalservice.ris.caselaw.domain.ReferenceType; import de.bund.digitalservice.ris.caselaw.domain.lookuptable.LegalPeriodical; import java.util.List; import java.util.UUID; @@ -38,6 +39,7 @@ private static Stream provideReferencesTestData_toDomain() { .citation("2024, 123") .footnote("footnote") .referenceSupplement("Klammerzusatz") + .referenceType(ReferenceType.CASELAW) .documentationUnit(createTestRelatedDocument()) .legalPeriodical(createTestLegalPeriodical()) .build()), @@ -55,6 +57,7 @@ private static Stream provideReferencesTestData_toDomain() { .citation("2024, 123") .footnote("footnote") .referenceSupplement("Klammerzusatz") + .referenceType(ReferenceType.CASELAW) .legalPeriodicalRawValue("LPA") .documentationUnit(createTestRelatedDocument()) .build())); @@ -87,6 +90,7 @@ private static Stream provideReferencesTestData_toDTO() { .citation("2024, S.5") .footnote("a footnote") .referenceSupplement("Klammerzusatz") + .referenceType(ReferenceType.CASELAW) .build(), ReferenceDTO.builder() .id(referenceId) @@ -114,6 +118,7 @@ private static Stream provideReferencesTestData_toDTO() { .primaryReference(true) .build()) .citation("2024, S.5") + .referenceType(ReferenceType.CASELAW) .build(), ReferenceDTO.builder() .rank(1) @@ -136,6 +141,7 @@ private static Stream provideReferencesTestData_toDTO() { .primaryReference(false) .build()) .citation("2024, S.5") + .referenceType(ReferenceType.CASELAW) .build(), ReferenceDTO.builder() .rank(1) @@ -150,7 +156,11 @@ private static Stream provideReferencesTestData_toDTO() { .build()), // possible with no legalPeriodical Arguments.of( - Reference.builder().citation("2024, S.5").legalPeriodicalRawValue("ABC").build(), + Reference.builder() + .citation("2024, S.5") + .legalPeriodicalRawValue("ABC") + .referenceType(ReferenceType.CASELAW) + .build(), ReferenceDTO.builder() .rank(1) .citation("2024, S.5") diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentTypeIntegrationTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentTypeIntegrationTest.java index 51e33de090..aee0c8a1ee 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentTypeIntegrationTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentTypeIntegrationTest.java @@ -78,7 +78,7 @@ void testGetAllDocumentTypes() { .consumeWith( response -> { assertThat(response.getResponseBody()) - .extracting("jurisShortcut", "label") + .extracting("label", "jurisShortcut") .containsExactly( Tuple.tuple("Amtsrechtliche Anordnung", "AmA"), Tuple.tuple("Anordnung", "Ao"), @@ -87,6 +87,26 @@ void testGetAllDocumentTypes() { }); } + @Test + void testGetAllDependantLiteratureDocumentTypes() { + risWebTestClient + .withDefaultLogin() + .get() + .uri("/api/v1/caselaw/documenttypes/dependent-literature") + .exchange() + .expectStatus() + .isOk() + .expectBody(new TypeReference>() {}) + .consumeWith( + response -> { + assertThat(response.getResponseBody()) + .extracting("label", "jurisShortcut") + .containsExactly( + Tuple.tuple("Anmerkung", "Ean"), + Tuple.tuple("Entscheidungsbesprechung", "Ebs")); + }); + } + @Test void testGetDocumentTypesWithQuery() { risWebTestClient @@ -100,10 +120,28 @@ void testGetDocumentTypesWithQuery() { .consumeWith( response -> { assertThat(response.getResponseBody()) - .extracting("jurisShortcut", "label") + .extracting("label", "jurisShortcut") .containsExactly( - Tuple.tuple("Anordnung", "Ao"), - Tuple.tuple("Amtsrechtliche Anordnung", "AmA")); + Tuple.tuple("Amtsrechtliche Anordnung", "AmA"), + Tuple.tuple("Anordnung", "Ao")); + }); + } + + @Test + void testGetDependantLiteratureDocumentTypesWithQuery() { + risWebTestClient + .withDefaultLogin() + .get() + .uri("/api/v1/caselaw/documenttypes/dependent-literature?q=Ea") + .exchange() + .expectStatus() + .isOk() + .expectBody(new TypeReference>() {}) + .consumeWith( + response -> { + assertThat(response.getResponseBody()) + .extracting("label", "jurisShortcut") + .containsExactly(Tuple.tuple("Anmerkung", "Ean")); }); } } diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/HandoverMailIntegrationTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/HandoverMailIntegrationTest.java index de2d953d19..b4ef31d599 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/HandoverMailIntegrationTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/HandoverMailIntegrationTest.java @@ -25,6 +25,7 @@ import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseHandoverReportRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseLegalPeriodicalEditionRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseLegalPeriodicalRepository; +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseReferenceRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseXmlHandoverMailRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentationOfficeDTO; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentationUnitDTO; @@ -140,6 +141,7 @@ static void registerDynamicProperties(DynamicPropertyRegistry registry) { @Autowired private RisWebTestClient risWebTestClient; @Autowired private DatabaseDocumentationUnitRepository repository; @Autowired private DatabaseLegalPeriodicalEditionRepository editionRepository; + @Autowired private DatabaseReferenceRepository referenceRepository; @Autowired private LegalPeriodicalRepository legalPeriodicalRepository; @Autowired private DatabaseLegalPeriodicalRepository dblegalPeriodicalRepository; @Autowired private DatabaseXmlHandoverMailRepository xmlHandoverRepository; @@ -210,13 +212,14 @@ void testHandover(HandoverEntityType entityType) { .build(); legalPeriodicalEditionDTO.setReferences( List.of( - ReferenceDTO.builder() - .id(UUID.randomUUID()) - .citation("citation") - .legalPeriodicalRawValue("ABC") - .rank(1) - .documentationUnit(savedDocumentationUnitDTO) - .build())); + referenceRepository.save( + ReferenceDTO.builder() + .id(UUID.randomUUID()) + .citation("citation") + .legalPeriodicalRawValue("ABC") + .rank(1) + .documentationUnit(savedDocumentationUnitDTO) + .build()))); editionRepository.save(legalPeriodicalEditionDTO); assertThat(editionRepository.findAll()).hasSize(1); identifier = "edition-" + entityId; diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/LegalPeriodicalEditionIntegrationTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/LegalPeriodicalEditionIntegrationTest.java index 22f28c0220..92e7b9121b 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/LegalPeriodicalEditionIntegrationTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/LegalPeriodicalEditionIntegrationTest.java @@ -36,6 +36,7 @@ import de.bund.digitalservice.ris.caselaw.domain.LegalPeriodicalRepository; import de.bund.digitalservice.ris.caselaw.domain.ProcedureService; import de.bund.digitalservice.ris.caselaw.domain.Reference; +import de.bund.digitalservice.ris.caselaw.domain.ReferenceType; import de.bund.digitalservice.ris.caselaw.domain.RelatedDocumentationUnit; import de.bund.digitalservice.ris.caselaw.domain.UserService; import de.bund.digitalservice.ris.caselaw.domain.exception.DocumentationUnitNotExistsException; @@ -312,6 +313,7 @@ void testGetLegalPeriodicalEditionsWithReferences() throws DocumentationUnitNotE List.of( Reference.builder() .id(existingReferenceId) + .referenceType(ReferenceType.CASELAW) .citation("New Citation") .legalPeriodicalRawValue("B") .documentationUnit( @@ -322,6 +324,7 @@ void testGetLegalPeriodicalEditionsWithReferences() throws DocumentationUnitNotE .build(), Reference.builder() .id(newReferenceId) + .referenceType(ReferenceType.CASELAW) .citation("New Reference") .legalPeriodicalRawValue("D") .documentationUnit( @@ -420,6 +423,7 @@ void testGetLegalPeriodicalEditionsWithDocUnitCreatedByReference() List.of( Reference.builder() .id(referenceId) + .referenceType(ReferenceType.CASELAW) .citation("ABC 2024, 3") .legalPeriodicalRawValue("ABC") .documentationUnit( @@ -520,6 +524,7 @@ void testCleanupDocUnitReferencesAndSourceWhenReferenceDeleted() List.of( Reference.builder() .id(referenceId) + .referenceType(ReferenceType.CASELAW) .citation("ABC 2024, 3") .legalPeriodicalRawValue("ABC") .documentationUnit( diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/ReferenceIntegrationTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/ReferenceIntegrationTest.java index ecaff4b477..582e29027d 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/ReferenceIntegrationTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/ReferenceIntegrationTest.java @@ -17,9 +17,11 @@ import de.bund.digitalservice.ris.caselaw.adapter.DocxConverterService; import de.bund.digitalservice.ris.caselaw.adapter.LdmlExporterService; import de.bund.digitalservice.ris.caselaw.adapter.OAuthService; +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseDocumentTypeRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseDocumentationOfficeRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseDocumentationUnitRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseLegalPeriodicalRepository; +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentTypeDTO; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentationOfficeDTO; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentationUnitDTO; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.LegalPeriodicalDTO; @@ -38,8 +40,10 @@ import de.bund.digitalservice.ris.caselaw.domain.HandoverService; import de.bund.digitalservice.ris.caselaw.domain.MailService; import de.bund.digitalservice.ris.caselaw.domain.Reference; +import de.bund.digitalservice.ris.caselaw.domain.ReferenceType; import de.bund.digitalservice.ris.caselaw.domain.UserService; import de.bund.digitalservice.ris.caselaw.domain.lookuptable.LegalPeriodical; +import de.bund.digitalservice.ris.caselaw.domain.lookuptable.documenttype.DocumentType; import de.bund.digitalservice.ris.caselaw.domain.mapper.PatchMapperService; import de.bund.digitalservice.ris.caselaw.webtestclient.RisWebTestClient; import java.util.List; @@ -92,6 +96,7 @@ static void registerDynamicProperties(DynamicPropertyRegistry registry) { @Autowired private DatabaseDocumentationUnitRepository repository; @Autowired private DatabaseDocumentationOfficeRepository documentationOfficeRepository; @Autowired private DatabaseLegalPeriodicalRepository legalPeriodicalRepository; + @Autowired private DatabaseDocumentTypeRepository documentTypeRepository; @MockBean private S3AsyncClient s3AsyncClient; @MockBean private MailService mailService; @@ -163,6 +168,7 @@ void testReferencesCanBeSaved() { .citation("2024, S.3") .referenceSupplement("Klammerzusatz") .footnote("footnote") + .referenceType(ReferenceType.CASELAW) .legalPeriodical( LegalPeriodical.builder() .uuid(legalPeriodical.getId()) @@ -196,4 +202,92 @@ void testReferencesCanBeSaved() { .isEqualTo(List.of(expectedLegalPeriodical)); }); } + + @Test + void testLiteratureReferencesCanBeSaved() { + DocumentationUnitDTO dto = + EntityBuilderTestUtil.createAndSavePublishedDocumentationUnit( + repository, documentationOffice); + + LegalPeriodicalDTO legalPeriodical = + legalPeriodicalRepository.save( + LegalPeriodicalDTO.builder() + .abbreviation("BVerwGE") + .title("Bundesverwaltungsgerichtsentscheidungen") + .subtitle("Entscheidungen des Bundesverwaltungsgerichts") + .jurisId(123) + .primaryReference(true) + .build()); + + LegalPeriodical expectedLegalPeriodical = + LegalPeriodical.builder() + .uuid(legalPeriodical.getId()) + .title(legalPeriodical.getTitle()) + .subtitle(legalPeriodical.getSubtitle()) + .abbreviation(legalPeriodical.getAbbreviation()) + .primaryReference(true) + .build(); + + DocumentTypeDTO documentTypeDTO = + documentTypeRepository.save( + DocumentTypeDTO.builder() + .label("Anmerkung") + .abbreviation("Ean") + .multiple(false) + .build()); + + DocumentType documentType = + DocumentType.builder() + .uuid(documentTypeDTO.getId()) + .jurisShortcut(documentTypeDTO.getAbbreviation()) + .label(documentTypeDTO.getLabel()) + .build(); + + UUID referenceId = UUID.randomUUID(); + DocumentationUnit documentationUnitFromFrontend = + DocumentationUnit.builder() + .uuid(dto.getId()) + .documentNumber(dto.getDocumentNumber()) + .coreData(CoreData.builder().documentationOffice(docOffice).build()) + .references( + List.of( + Reference.builder() + .id(referenceId) + .citation("2024, S.3") + .author("Heinz Otto") + .documentType(documentType) + .referenceType(ReferenceType.LITERATURE) + .legalPeriodical( + LegalPeriodical.builder() + .uuid(legalPeriodical.getId()) + .abbreviation("BVerwGE") + .primaryReference(true) + .build()) + .build())) + .build(); + + risWebTestClient + .withDefaultLogin() + .put() + .uri("/api/v1/caselaw/documentunits/" + dto.getId()) + .bodyValue(documentationUnitFromFrontend) + .exchange() + .expectStatus() + .isOk() + .expectBody(DocumentationUnit.class) + .consumeWith( + response -> { + assertThat(response.getResponseBody()).isNotNull(); + assertThat(response.getResponseBody().documentNumber()) + .isEqualTo(DEFAULT_DOCUMENT_NUMBER); + assertThat(response.getResponseBody().references()).hasSize(1); + assertThat(response.getResponseBody().references()) + .extracting("citation", "author", "documentType", "id") + .containsExactly(tuple("2024, S.3", "Heinz Otto", documentType, referenceId)); + assertThat(response.getResponseBody().references()) + .extracting("legalPeriodical") + .usingRecursiveComparison() + .isEqualTo(List.of(expectedLegalPeriodical)); + }); + } } diff --git a/backend/src/test/resources/document_types.sql b/backend/src/test/resources/document_types.sql index 6cf3b41c9a..2bf45fa57e 100644 --- a/backend/src/test/resources/document_types.sql +++ b/backend/src/test/resources/document_types.sql @@ -1,43 +1,54 @@ insert into - incremental_migration.document_category (id, label) + incremental_migration.document_category (id, label) values - ('0f382996-497f-4c2f-9a30-1c73d8ac0a88', 'R'), - ('11defe05-cd4d-43e5-a07e-06c611b81a28', 'S') on conflict -do - nothing; + ('0f382996-497f-4c2f-9a30-1c73d8ac0a88', 'R'), + ('4879ae8e-e809-4dd7-8517-d5c795bead79', 'U'), + ('11defe05-cd4d-43e5-a07e-06c611b81a28', 'S') +on conflict do nothing; insert into - incremental_migration.document_type (id, abbreviation, label, document_category_id) + incremental_migration.document_type (id, abbreviation, label, document_category_id) values - ( - '0f382996-497f-4c2f-9a30-1c73d8ac0a87', - 'Beschluss', - 'Bes', - '0f382996-497f-4c2f-9a30-1c73d8ac0a88' - ), - ( - '11defe05-cd4d-43e5-a07e-06c611b81a26', - 'Urteil', - 'Ur', - '0f382996-497f-4c2f-9a30-1c73d8ac0a88' - ), - ( - '2e5bab9c-8852-49a3-8ed8-08b67399abde', - 'Anordnung', - 'Ao', - '0f382996-497f-4c2f-9a30-1c73d8ac0a88' - ), - ( - '3fc6c738-7e81-40bd-8aa4-c5426605e9b0', - 'Amtsrechtliche Anordnung', - 'AmA', - '0f382996-497f-4c2f-9a30-1c73d8ac0a88' - ), - ( - '1f382996-4924-4c2f-9a30-1c73d8ac0a87', - 'Andere', - 'And', - '11defe05-cd4d-43e5-a07e-06c611b81a28' - ) on conflict -do - nothing; + ( + '0f382996-497f-4c2f-9a30-1c73d8ac0a87', + 'Bes', + 'Beschluss', + '0f382996-497f-4c2f-9a30-1c73d8ac0a88' + ), + ( + '11defe05-cd4d-43e5-a07e-06c611b81a26', + 'Ur', + 'Urteil', + '0f382996-497f-4c2f-9a30-1c73d8ac0a88' + ), + ( + '2e5bab9c-8852-49a3-8ed8-08b67399abde', + 'Ao', + 'Anordnung', + '0f382996-497f-4c2f-9a30-1c73d8ac0a88' + ), + ( + '3fc6c738-7e81-40bd-8aa4-c5426605e9b0', + 'AmA', + 'Amtsrechtliche Anordnung', + '0f382996-497f-4c2f-9a30-1c73d8ac0a88' + ), + ( + '1f382996-4924-4c2f-9a30-1c73d8ac0a87', + 'And', + 'Andere', + '11defe05-cd4d-43e5-a07e-06c611b81a28' + ), + ( + 'f718a7ee-f419-46cf-a96a-29227927850c', + 'Ean', + 'Anmerkung', + '4879ae8e-e809-4dd7-8517-d5c795bead79' + ), + ( + '198b276e-8e6d-4df6-8692-44d74ed4fcba', + 'Ebs', + 'Entscheidungsbesprechung', + '4879ae8e-e809-4dd7-8517-d5c795bead79' + ) +on conflict do nothing; diff --git a/frontend/src/components/DocumentUnitReferenceInput.vue b/frontend/src/components/DocumentUnitReferenceInput.vue index 99877f5c28..55611615e2 100644 --- a/frontend/src/components/DocumentUnitReferenceInput.vue +++ b/frontend/src/components/DocumentUnitReferenceInput.vue @@ -1,7 +1,8 @@ @/stores/editionStore diff --git a/frontend/src/components/periodical-evaluation/references/PeriodicalEditionReferenceSummary.vue b/frontend/src/components/periodical-evaluation/references/PeriodicalEditionReferenceSummary.vue index 9182127bd5..f7c9bc48c8 100644 --- a/frontend/src/components/periodical-evaluation/references/PeriodicalEditionReferenceSummary.vue +++ b/frontend/src/components/periodical-evaluation/references/PeriodicalEditionReferenceSummary.vue @@ -15,16 +15,15 @@ const props = defineProps<{ v-if="props.data.documentationUnit?.documentNumber" class="flex flex-col" > +
+ {{ props.data.renderDecision }} +
- -
- {{ props.data.renderDecision }} -
diff --git a/frontend/src/domain/reference.ts b/frontend/src/domain/reference.ts index 14861f93c3..9438e74af8 100644 --- a/frontend/src/domain/reference.ts +++ b/frontend/src/domain/reference.ts @@ -1,3 +1,4 @@ +import { DocumentType } from "./documentUnit" import EditableListItem from "./editableListItem" import RelatedDocumentation from "./relatedDocumentation" import LegalPeriodical from "@/domain/legalPeriodical" @@ -10,6 +11,9 @@ export default class Reference implements EditableListItem { legalPeriodical?: LegalPeriodical legalPeriodicalRawValue?: string documentationUnit?: RelatedDocumentation + documentType?: DocumentType + author?: string + referenceType: "caselaw" | "literature" = "caselaw" static readonly requiredFields = [ "legalPeriodical", @@ -17,11 +21,25 @@ export default class Reference implements EditableListItem { "referenceSupplement", ] as const + static readonly requiredFieldsForDocunit = [ + "legalPeriodical", + "citation", + ] as const + + static readonly requiredLiteratureFields = [ + "legalPeriodical", + "citation", + "documentType", + "author", + ] as const + static readonly fields = [ "legalPeriodical", "citation", "referenceSupplement", "documentationUnit", + "author", + "documentType", ] as const constructor(data: Partial = {}) { @@ -46,9 +64,11 @@ export default class Reference implements EditableListItem { get renderDecision(): string { return [ + this.author ? `${this.author},` : "", this.legalPeriodical?.abbreviation ?? this.legalPeriodicalRawValue, this.citation, this.referenceSupplement ? ` (${this.referenceSupplement})` : "", + this.documentType ? ` (${this.documentType.jurisShortcut})` : "", ] .filter(Boolean) .join(" ") @@ -64,6 +84,26 @@ export default class Reference implements EditableListItem { ) } + get hasMissingRequiredFieldsForDocunit(): boolean { + return this.missingRequiredFieldsForDocunit.length > 0 + } + + get missingRequiredFieldsForDocunit() { + return Reference.requiredFieldsForDocunit.filter((field) => + this.fieldIsEmpty(this[field]), + ) + } + + get hasMissingRequiredLiteratureFields(): boolean { + return this.missingRequiredLiteratureFields.length > 0 + } + + get missingRequiredLiteratureFields() { + return Reference.requiredLiteratureFields.filter((field) => + this.fieldIsEmpty(this[field]), + ) + } + equals(entry: Reference): boolean { return this.id === entry.id } diff --git a/frontend/src/services/comboboxItemService.ts b/frontend/src/services/comboboxItemService.ts index 849e729820..489a5f8b1b 100644 --- a/frontend/src/services/comboboxItemService.ts +++ b/frontend/src/services/comboboxItemService.ts @@ -13,6 +13,7 @@ import errorMessages from "@/i18n/errors.json" enum Endpoint { documentTypes = "documenttypes", + dependentLiteratureDocumentTypes = "documenttypes/dependent-literature", courts = "courts", citationTypes = "citationtypes", fieldOfLawSearchByIdentifier = "fieldsoflaw/search-by-identifier", @@ -29,7 +30,8 @@ function formatDropdownItems( endpoint: Endpoint, ): ComboboxItem[] { switch (endpoint) { - case Endpoint.documentTypes: { + case Endpoint.documentTypes: + case Endpoint.dependentLiteratureDocumentTypes: { return (responseData as DocumentType[]).map((item) => ({ label: item.label, value: item, @@ -153,6 +155,8 @@ const service: ComboboxItemService = { await fetchFromEndpoint(Endpoint.courts, filter), getDocumentTypes: async (filter?: string) => await fetchFromEndpoint(Endpoint.documentTypes, filter), + getDependentLiteratureDocumentTypes: async (filter?: string) => + await fetchFromEndpoint(Endpoint.dependentLiteratureDocumentTypes, filter), getFieldOfLawSearchByIdentifier: async (filter?: string) => await fetchFromEndpoint(Endpoint.fieldOfLawSearchByIdentifier, filter), getRisAbbreviations: async (filter?: string) => diff --git a/frontend/test/components/periodicalEvaluation/editionReferences.spec.ts b/frontend/test/components/periodicalEvaluation/editionReferences.spec.ts index 7dcca11373..d77390a16b 100644 --- a/frontend/test/components/periodicalEvaluation/editionReferences.spec.ts +++ b/frontend/test/components/periodicalEvaluation/editionReferences.spec.ts @@ -137,6 +137,8 @@ describe("Legal periodical edition evaluation", () => { test("renders legal periodical reference input", async () => { await renderComponent() + expect(screen.getByLabelText("Rechtsprechung Fundstelle")).toBeChecked() + expect(screen.getByLabelText("Literatur Fundstelle")).not.toBeChecked() expect( screen.getByLabelText("Zitatstelle Präfix", { exact: true }), ).toHaveValue("präfix") @@ -164,6 +166,34 @@ describe("Legal periodical edition evaluation", () => { ).toBeInTheDocument() }) + test("toggles input fields, when changing reference type", async () => { + const { user } = await renderComponent() + + expect(screen.getByLabelText("Rechtsprechung Fundstelle")).toBeChecked() + expect(screen.getByLabelText("Literatur Fundstelle")).not.toBeChecked() + expect( + screen.getByLabelText("Klammernzusatz", { exact: true }), + ).toBeVisible() + expect( + screen.queryByLabelText("Dokumenttyp Literaturfundstelle"), + ).not.toBeInTheDocument() + expect( + screen.queryByLabelText("Autor Literaturfundstelle"), + ).not.toBeInTheDocument() + + await user.click(screen.getByLabelText("Literatur Fundstelle")) + + expect(screen.getByLabelText("Rechtsprechung Fundstelle")).not.toBeChecked() + expect(screen.getByLabelText("Literatur Fundstelle")).toBeChecked() + expect( + screen.queryByLabelText("Klammernzusatz", { exact: true }), + ).not.toBeInTheDocument() + expect( + screen.getByLabelText("Dokumenttyp Literaturfundstelle"), + ).toBeVisible() + expect(screen.getByLabelText("Autor Literaturfundstelle")).toBeVisible() + }) + test("deletes documentation unit created by reference when selected", async () => { const user = await editReferenceWhichCreatedDocUnitOfOwnOffice() await user.click(screen.getByLabelText("Eintrag löschen")) diff --git a/frontend/test/e2e/caselaw/categories/references.spec.ts b/frontend/test/e2e/caselaw/categories/references.spec.ts index 9c7eb58488..04c7f68956 100644 --- a/frontend/test/e2e/caselaw/categories/references.spec.ts +++ b/frontend/test/e2e/caselaw/categories/references.spec.ts @@ -244,6 +244,106 @@ test.describe( }, ) + test( + "Literature references can be added to documentation unit", + { + tag: "@RISDEV-5236 @RISDEV-5454", + }, + async ({ page, documentNumber }) => { + await test.step("Caselaw reference type is preselected", async () => { + await navigateToReferences(page, documentNumber) + + await expect( + page.getByLabel("Rechtsprechung Fundstelle"), + ).toBeChecked() + + await expect( + page.getByLabel("Literatur Fundstelle"), + ).not.toBeChecked() + + await expect(page.getByLabel("Klammernzusatz")).toBeVisible() + + await expect( + page.getByLabel("Dokumenttyp Literaturfundstelle"), + ).toBeHidden() + + await expect( + page.getByLabel("Autor Literaturfundstelle"), + ).toBeHidden() + }) + + await test.step("Selecting literature reference type, renders different inputs", async () => { + await page.getByLabel("Literatur Fundstelle").click() + await expect( + page.getByLabel("Rechtsprechung Fundstelle"), + ).not.toBeChecked() + + await expect(page.getByLabel("Literatur Fundstelle")).toBeChecked() + + await expect( + page.getByLabel("Dokumenttyp Literaturfundstelle"), + ).toBeVisible() + + await expect( + page.getByLabel("Autor Literaturfundstelle"), + ).toBeVisible() + await expect(page.getByLabel("Klammernzusatz")).toBeHidden() + }) + + await test.step("Literature references are validated for required inputs", async () => { + await fillInput(page, "Periodikum", "AllMBl") + await page + .getByText("AllMBl | Allgemeines Ministerialblatt", { + exact: true, + }) + .click() + await waitForInputValue(page, "[aria-label='Periodikum']", "AllMBl") + await fillInput(page, "Zitatstelle", "2024, 2") + + await page.locator("[aria-label='Fundstelle speichern']").click() + // check that both fields display error message + await expect( + page.locator("text=Pflichtfeld nicht befüllt"), + ).toHaveCount(2) + + // Switching between radio buttons resets the validation errors + await page.getByLabel("Rechtsprechung Fundstelle").click() + await page.getByLabel("Literatur Fundstelle").click() + await expect( + page.locator("text=Pflichtfeld nicht befüllt"), + ).toHaveCount(0) + }) + + await test.step("Save literature reference, verify that it is shown in the list", async () => { + await fillInput(page, "Autor Literaturfundstelle", "Bilen, Ulviye") + await fillInput(page, "Dokumenttyp Literaturfundstelle", "Ean") + await page.getByText("Ean", { exact: true }).click() + await waitForInputValue( + page, + "[aria-label='Dokumenttyp Literaturfundstelle']", + "Anmerkung", + ) + await page.locator("[aria-label='Fundstelle speichern']").click() + await expect( + page.getByText("Bilen, Ulviye, AllMBl 2024, 2 (Ean)"), + ).toBeVisible() + }) + + // await test.step("Literature reference are shown in correct order", async () => { + + // }) + + await test.step("Radio buttons should not be visible after saving", async () => { + await page.getByTestId("list-entry-0").click() + await expect( + page.getByLabel("Rechtsprechung Fundstelle"), + ).toBeHidden() + + await expect(page.getByLabel("Literatur Fundstelle")).toBeHidden() + }) + }, + ) + test( "References are visible in preview", { diff --git a/frontend/test/e2e/caselaw/docunit-creation-from-evaluation.spec.ts b/frontend/test/e2e/caselaw/docunit-creation-from-evaluation.spec.ts index bb83cd1983..e17c53f7aa 100644 --- a/frontend/test/e2e/caselaw/docunit-creation-from-evaluation.spec.ts +++ b/frontend/test/e2e/caselaw/docunit-creation-from-evaluation.spec.ts @@ -79,6 +79,7 @@ test.describe( .locator("#documentationUnit") .getByLabel("Auswahl zurücksetzen") .click() + await page.keyboard.press("Escape") await page.getByText("Suchen").click() await waitForInputValue(page, "[aria-label='Gericht']", "") await waitForInputValue( @@ -96,7 +97,11 @@ test.describe( await test.step("Foreign courts are not assigned to a responsible doc office", async () => { await fillInput(page, "Gericht", "Arbeits- und Sozialgericht Wien") await page.getByText("Arbeits- und Sozialgericht Wien").click() + + const requestFinishedPromise = page.waitForEvent("requestfinished") await page.getByText("Suchen").click() + await requestFinishedPromise + await waitForInputValue( page, "[aria-label='Zuständige Dokumentationsstelle']", @@ -117,8 +122,8 @@ test.describe( page.locator("[aria-label='dropdown-option'] >> nth=6"), ).toBeVisible() - await expect(page.getByText("BAG")).toBeVisible() - await expect(page.getByText("BFH")).toBeVisible() + await expect(page.getByText("BAG", { exact: true })).toBeVisible() + await expect(page.getByText("BFH", { exact: true })).toBeVisible() await fillInput(page, "Zuständige Dokumentationsstelle", "bv") await waitForInputValue( @@ -127,9 +132,9 @@ test.describe( "bv", ) - await expect(page.getByText("BAG")).toBeHidden() - await expect(page.getByText("BFH")).toBeHidden() - await expect(page.getByText("BVerwG")).toBeVisible() + await expect(page.getByText("BAG", { exact: true })).toBeHidden() + await expect(page.getByText("BFH", { exact: true })).toBeHidden() + await expect(page.getByText("BVerwG", { exact: true })).toBeVisible() await expect( page.locator("button").filter({ hasText: "BVerfG" }), ).toBeVisible() @@ -351,7 +356,11 @@ test.describe( ).toBeVisible() await newTab.getByLabel("Fundstellen").click() - await expect(newTab.getByText("Fundstellen bearbeiten")).toBeVisible() + await expect( + newTab + .getByTestId("references-preview") + .getByText("Fundstellen", { exact: true }), + ).toBeVisible() await expect( newTab .getByLabel("Listen Eintrag") @@ -398,6 +407,12 @@ test.describe( .locator('button:has-text("Dokumentationseinheit löschen")') .click() + await expect( + pageWithBghUser.locator( + 'button:has-text("Dokumentationseinheit löschen")', + ), + ).toBeHidden() + await expect( pageWithBghUser.getByText( `AG Aachen, ${formattedDate}, ${randomFileNumber}, Anerkenntnisurteil, Unveröffentlicht`, @@ -610,191 +625,171 @@ test.describe( await expect(page.getByLabel("Zitatstelle Suffix")).toHaveValue( edition.suffix!, ) - try { - await test.step("Creating docoffice creates a documentunit for owning docoffice and has edit rights", async () => { - await fillInput(page, "Zitatstelle *", "12") - await fillInput(page, "Klammernzusatz", "L") - await searchForDocUnit( - page, - "AG Aachen", - formattedDate, - generateString(), - "AnU", - ) - - await expect( - page.getByLabel("Zuständige Dokumentationsstelle"), - ).toHaveValue("BGH") - }) - - await test.step("Creating docoffice has access to created docunit", async () => { - const pagePromise = page.context().waitForEvent("page") - await page.getByText("Übernehmen und weiter bearbeiten").click() - const newTab = await pagePromise - await expect(newTab).toHaveURL( - /\/caselaw\/documentunit\/[A-Z0-9]{13}\/categories$/, - ) - documentNumber1 = /caselaw\/documentunit\/(.*)\/categories/g.exec( - newTab.url(), - )?.[1] as string - await expect(page.getByLabel("Listen Eintrag")).toHaveCount(2) - }) - - await test.step("Creating docoffice creates a second documentunit for owning docoffice", async () => { - await fillInput(page, "Zitatstelle *", "12") - await fillInput(page, "Klammernzusatz", "L") - await searchForDocUnit( - page, - "AG Aachen", - formattedDate, - generateString(), - "AnU", - ) - - await expect( - page.getByLabel("Zuständige Dokumentationsstelle"), - ).toHaveValue("BGH") - await page.getByText("Übernehmen und weiter bearbeiten").click() - const pagePromise = page.context().waitForEvent("page") - - const newTab = await pagePromise - await expect(newTab).toHaveURL( - /\/caselaw\/documentunit\/[A-Z0-9]{13}\/categories$/, - ) - documentNumber2 = /caselaw\/documentunit\/(.*)\/categories/g.exec( - newTab.url(), - )?.[1] as string - await expect(page.getByLabel("Listen Eintrag")).toHaveCount(3) - }) - - await test.step("Owning docoffice user can preview a created docunit from periodical evaluation", async () => { - await navigateToSearch(pageWithBghUser) - - await pageWithBghUser - .getByLabel("Dokumentnummer Suche") - .fill(documentNumber1) - - const select = pageWithBghUser.locator(`select[id="status"]`) - await select.selectOption("Fremdanlage") - await pageWithBghUser - .getByLabel("Nach Dokumentationseinheiten suchen") - .click() - const listEntry = pageWithBghUser.getByRole("row") - await expect(listEntry).toHaveCount(1) - - await expect( - pageWithBghUser.getByLabel("Dokumentationseinheit ansehen"), - ).toBeVisible() - await pageWithBghUser - .getByLabel("Dokumentationseinheit ansehen") - .click() - const pagePromise = pageWithBghUser.context().waitForEvent("page") - - const newTab = await pagePromise - await expect(newTab).toHaveURL( - /\/caselaw\/documentunit\/[A-Z0-9]{13}\/preview$/, - ) - }) - - await test.step("Owning docoffice can accept a created docunit from periodical evaluation, which changes the status to unpublished", async () => { - await navigateToSearch(pageWithBghUser) - - await pageWithBghUser - .getByLabel("Dokumentnummer Suche") - .fill(documentNumber1) - - const select = pageWithBghUser.locator(`select[id="status"]`) - await select.selectOption("Fremdanlage") - await pageWithBghUser - .getByLabel("Nach Dokumentationseinheiten suchen") - .click() - const listEntry = pageWithBghUser.getByRole("row") - await expect(listEntry).toHaveCount(1) - await expect(listEntry).toContainText( - `Fremdanlage aus MMG ${edition.prefix}12${edition.suffix} (DS)`, - ) - await expect( - pageWithBghUser.getByLabel("Dokumentationseinheit übernehmen"), - ).toBeVisible() - - await expect( - pageWithBghUser.getByText("Dokumentationseinheit bearbeiten"), - ).toBeHidden() - - await pageWithBghUser - .getByLabel("Dokumentationseinheit übernehmen") - .click() - - await expect( - pageWithBghUser.getByLabel("Dokumentationseinheit übernehmen"), - ).toBeHidden() - - await expect( - pageWithBghUser.getByLabel("Dokumentationseinheit bearbeiten"), - ).toBeVisible() - await expect(listEntry).toContainText(`Unveröffentlicht`) - }) - - // This is not yet working, because the docunit does not appear in the search for creating docoffice - // await test.step("Creating docoffice is no longer able to edit the created docunit", async () => { - // await navigateToSearch(page) - - // await page - // .getByLabel("Dokumentnummer Suche") - // .fill(documentNumber1) - - // await page - // .getByLabel("Nach Dokumentationseinheiten suchen") - // .click() - // const listEntry = pageWithBghUser.getByRole("row") - // await expect(listEntry).toHaveCount(1) - // await expect(listEntry).toContainText(`Unveröffentlicht`) - - // await expect( - // pageWithBghUser.getByText("Dokumentationseinheit bearbeiten"), - // ).toBeDisabled() - // }) - - await test.step("Owning docoffice can delete a created docunit from periodical evaluation, which also deletes the reference in the edition", async () => { - await navigateToSearch(pageWithBghUser) - - await pageWithBghUser - .getByLabel("Dokumentnummer Suche") - .fill(documentNumber2) - - const select = pageWithBghUser.locator(`select[id="status"]`) - await select.selectOption("Fremdanlage") - await pageWithBghUser - .getByLabel("Nach Dokumentationseinheiten suchen") - .click() - const listEntry = pageWithBghUser.getByRole("row") - await expect(listEntry).toHaveCount(1) - await expect(listEntry).toContainText( - `Fremdanlage aus MMG ${edition.prefix}12${edition.suffix} (DS)`, - ) - await expect( - pageWithBghUser.getByLabel("Dokumentationseinheit löschen"), - ).toBeVisible() - - await expect( - pageWithBghUser.getByText("Dokumentationseinheit bearbeiten"), - ).toBeHidden() - - await pageWithBghUser - .getByLabel("Dokumentationseinheit löschen") - .click() - - await pageWithBghUser.locator('button:has-text("Löschen")').click() - - await expect(pageWithBghUser.getByRole("row")).toHaveCount(0) - - await page.reload() - await expect(page.getByText(documentNumber2)).toBeHidden() - await expect(page.getByLabel("Listen Eintrag")).toHaveCount(2) - }) - } finally { - await deleteDocumentUnit(pageWithBghUser, documentNumber1) - } + await test.step("Creating docoffice creates a documentunit for owning docoffice and has edit rights", async () => { + await fillInput(page, "Zitatstelle *", "12") + await waitForInputValue(page, "[aria-label='Zitatstelle *']", "12") + await fillInput(page, "Klammernzusatz", "L") + await searchForDocUnit( + page, + "AG Aachen", + formattedDate, + generateString(), + "AnU", + ) + + await expect( + page.getByLabel("Zuständige Dokumentationsstelle"), + ).toHaveValue("BGH") + }) + + await test.step("Creating docoffice has access to created docunit", async () => { + const pagePromise = page.context().waitForEvent("page") + await page.getByText("Übernehmen und weiter bearbeiten").click() + const newTab = await pagePromise + await expect(newTab).toHaveURL( + /\/caselaw\/documentunit\/[A-Z0-9]{13}\/categories$/, + ) + documentNumber1 = /caselaw\/documentunit\/(.*)\/categories/g.exec( + newTab.url(), + )?.[1] as string + await expect(page.getByLabel("Listen Eintrag")).toHaveCount(2) + }) + + const fileNumberDocUnit2 = generateString() + + await test.step("Creating docoffice creates a second documentunit for owning docoffice", async () => { + await expect(page.getByLabel("Zitatstelle *")).toBeVisible() + + await fillInput(page, "Zitatstelle *", "12") + await fillInput(page, "Klammernzusatz", "L") + + await searchForDocUnit( + page, + "AG Aachen", + formattedDate, + fileNumberDocUnit2, + "AnU", + ) + + await expect( + page.getByLabel("Zuständige Dokumentationsstelle"), + ).toHaveValue("BGH") + await page.getByText("Übernehmen", { exact: true }).click() + + documentNumber2 = + // eslint-disable-next-line playwright/no-conditional-in-test + (await page + .getByTestId("document-number-link-" + fileNumberDocUnit2) + .getByRole("paragraph") + .textContent()) || "" + await expect(page.getByLabel("Listen Eintrag")).toHaveCount(3) + }) + + await test.step("Owning docoffice user can preview a created docunit from periodical evaluation", async () => { + await navigateToSearch(pageWithBghUser) + + await pageWithBghUser + .getByLabel("Dokumentnummer Suche") + .fill(documentNumber1) + + const select = pageWithBghUser.locator(`select[id="status"]`) + await select.selectOption("Fremdanlage") + await pageWithBghUser + .getByLabel("Nach Dokumentationseinheiten suchen") + .click() + const listEntry = pageWithBghUser.getByRole("row") + await expect(listEntry).toHaveCount(1) + + await expect( + pageWithBghUser.getByLabel("Dokumentationseinheit ansehen"), + ).toBeVisible() + const pagePromise = pageWithBghUser.context().waitForEvent("page") + await pageWithBghUser + .getByLabel("Dokumentationseinheit ansehen") + .click() + + const newTab = await pagePromise + await expect(newTab).toHaveURL( + /\/caselaw\/documentunit\/[A-Z0-9]{13}\/preview$/, + ) + }) + + await test.step("Owning docoffice can accept a created docunit from periodical evaluation, which changes the status to unpublished", async () => { + await navigateToSearch(pageWithBghUser) + + await pageWithBghUser + .getByLabel("Dokumentnummer Suche") + .fill(documentNumber1) + + const select = pageWithBghUser.locator(`select[id="status"]`) + await select.selectOption("Fremdanlage") + await pageWithBghUser + .getByLabel("Nach Dokumentationseinheiten suchen") + .click() + const listEntry = pageWithBghUser.getByRole("row") + await expect(listEntry).toHaveCount(1) + await expect(listEntry).toContainText( + `Fremdanlage aus MMG ${edition.prefix}12${edition.suffix} (DS)`, + ) + await expect( + pageWithBghUser.getByLabel("Dokumentationseinheit übernehmen"), + ).toBeVisible() + + await expect( + pageWithBghUser.getByText("Dokumentationseinheit bearbeiten"), + ).toBeHidden() + + await pageWithBghUser + .getByLabel("Dokumentationseinheit übernehmen") + .click() + + await expect( + pageWithBghUser.getByLabel("Dokumentationseinheit übernehmen"), + ).toBeHidden() + + await expect( + pageWithBghUser.getByLabel("Dokumentationseinheit bearbeiten"), + ).toBeVisible() + await expect(listEntry).toContainText(`Unveröffentlicht`) + }) + + await test.step("Owning docoffice can delete a created docunit from periodical evaluation, which also deletes the reference in the edition", async () => { + await navigateToSearch(pageWithBghUser) + + await pageWithBghUser + .getByLabel("Dokumentnummer Suche") + .fill(documentNumber2) + + const select = pageWithBghUser.locator(`select[id="status"]`) + await select.selectOption("Fremdanlage") + await pageWithBghUser + .getByLabel("Nach Dokumentationseinheiten suchen") + .click() + const listEntry = pageWithBghUser.getByRole("row") + await expect(listEntry).toHaveCount(1) + await expect(listEntry).toContainText( + `Fremdanlage aus MMG ${edition.prefix}12${edition.suffix} (DS)`, + ) + await expect( + pageWithBghUser.getByLabel("Dokumentationseinheit löschen"), + ).toBeVisible() + + await expect( + pageWithBghUser.getByText("Dokumentationseinheit bearbeiten"), + ).toBeHidden() + + await pageWithBghUser + .getByLabel("Dokumentationseinheit löschen") + .click() + + await pageWithBghUser.locator('button:has-text("Löschen")').click() + + await expect(pageWithBghUser.getByRole("row")).toHaveCount(0) + + await page.reload() + await expect(page.getByText(documentNumber2)).toBeHidden() + await expect(page.getByLabel("Listen Eintrag")).toHaveCount(2) + }) }, ) diff --git a/frontend/test/e2e/caselaw/fixtures.ts b/frontend/test/e2e/caselaw/fixtures.ts index 95abbe2995..ea708e5f8c 100644 --- a/frontend/test/e2e/caselaw/fixtures.ts +++ b/frontend/test/e2e/caselaw/fixtures.ts @@ -343,6 +343,7 @@ export const caselawTest = test.extend({ references: [ { id: crypto.randomUUID(), + referenceType: "caselaw", citation: "2024, 12-22, Heft 1", referenceSupplement: "L", legalPeriodicalRawValue: "MMG", @@ -354,6 +355,7 @@ export const caselawTest = test.extend({ }, { id: crypto.randomUUID(), + referenceType: "caselaw", citation: "2024, 1-11, Heft 1", legalPeriodicalRawValue: "MMG", legalPeriodical: legalPeriodical, diff --git a/frontend/test/e2e/caselaw/legal-periodical-evaluation.spec.ts b/frontend/test/e2e/caselaw/legal-periodical-evaluation.spec.ts index ebfd6aa304..20acdff2c7 100644 --- a/frontend/test/e2e/caselaw/legal-periodical-evaluation.spec.ts +++ b/frontend/test/e2e/caselaw/legal-periodical-evaluation.spec.ts @@ -572,6 +572,107 @@ test.describe( }, ) + test( + "Literature references can be added to periodical evaluation", + { + tag: "@RISDEV-5236 @RISDEV-5454", + }, + async ({ page, editionWithReferences, prefilledDocumentUnit }) => { + const fileNumber = prefilledDocumentUnit.coreData.fileNumbers?.[0] || "" + await test.step("Caselaw reference type is preselected", async () => { + await navigateToPeriodicalReferences( + page, + editionWithReferences.id || "", + ) + + await expect( + page.getByLabel("Rechtsprechung Fundstelle"), + ).toBeChecked() + + await expect( + page.getByLabel("Literatur Fundstelle"), + ).not.toBeChecked() + + await expect(page.getByLabel("Klammernzusatz")).toBeVisible() + + await expect( + page.getByLabel("Dokumenttyp Literaturfundstelle"), + ).toBeHidden() + + await expect( + page.getByLabel("Autor Literaturfundstelle"), + ).toBeHidden() + }) + + await test.step("Selecting literature reference type, renders different inputs", async () => { + await page.getByLabel("Literatur Fundstelle").click() + await expect( + page.getByLabel("Rechtsprechung Fundstelle"), + ).not.toBeChecked() + + await expect(page.getByLabel("Literatur Fundstelle")).toBeChecked() + + await expect(page.getByLabel("Klammernzusatz")).toBeHidden() + + await expect( + page.getByLabel("Dokumenttyp Literaturfundstelle"), + ).toBeVisible() + + await expect( + page.getByLabel("Autor Literaturfundstelle"), + ).toBeVisible() + }) + + await test.step("Literature references are validated for required inputs", async () => { + await fillInput(page, "Zitatstelle *", `2021, 2`) + + await searchForDocUnitWithFileNumber(page, fileNumber, "31.12.2019") + await page.getByLabel("Treffer übernehmen").click() + // check that both fields display error message + await expect( + page.locator("text=Pflichtfeld nicht befüllt"), + ).toHaveCount(2) + + // Switching between radio buttons resets the validation errors + await page.getByLabel("Rechtsprechung Fundstelle").click() + await page.getByLabel("Literatur Fundstelle").click() + await expect( + page.locator("text=Pflichtfeld nicht befüllt"), + ).toHaveCount(0) + }) + + await test.step("Save literature reference, verify that it is shown in the list", async () => { + await fillInput(page, "Autor Literaturfundstelle", "Bilen, Ulviye") + await fillInput(page, "Dokumenttyp Literaturfundstelle", "Ean") + await page.getByText("Ean", { exact: true }).click() + await waitForInputValue( + page, + "[aria-label='Dokumenttyp Literaturfundstelle']", + "Anmerkung", + ) + + await searchForDocUnitWithFileNumber(page, fileNumber, "31.12.2019") + await page.getByLabel("Treffer übernehmen").click() + await expect( + page.getByText("Bilen, Ulviye, MMG 2024, 2021, 2, Heft 1 (Ean)"), + ).toBeVisible() + }) + + // await test.step("Literature reference are shown in correct order", async () => { + + // }) + + await test.step("Radio buttons should not be visible after saving", async () => { + await page.getByTestId("list-entry-0").click() + await expect( + page.getByLabel("Rechtsprechung Fundstelle"), + ).toBeHidden() + + await expect(page.getByLabel("Literatur Fundstelle")).toBeHidden() + }) + }, + ) + // Flaky, needs some clarification // eslint-disable-next-line playwright/no-skipped-test test.skip(