From 478e1f5352331f4d824be7d47053cf6008c57a23 Mon Sep 17 00:00:00 2001 From: nbabai Date: Mon, 6 Jan 2025 11:59:06 +0100 Subject: [PATCH 1/3] Reset document number count on each year RISDEV-6004 * Adding year to document number table * Dropping old constraint on abbreviation and update on id * Adding combined index for year and doc office * Backfilling 2024 for former year entries * Retrieve document number by both doc office and year * Extracting getOrCreateDocumentNumberDTO to method * Adding tests --- ...atabaseDocumentNumberGeneratorService.java | 28 +++++++++---- .../jpa/DatabaseDocumentNumberRepository.java | 10 ++--- .../database/jpa/DocumentNumberDTO.java | 11 +++++- .../V1.11__add_id_to_document_number.sql | 13 +++++++ .../V1.12__add_year_to_document_number.sql | 13 +++++++ ...aseDocumentNumberGeneratorServiceTest.java | 39 +++++++++++++++++++ 6 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 backend/src/main/resources/db-scripts/migration/V1.11__add_id_to_document_number.sql create mode 100644 backend/src/main/resources/db-scripts/migration/V1.12__add_year_to_document_number.sql diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorService.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorService.java index a843ce367c..df02e53611 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorService.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorService.java @@ -79,13 +79,7 @@ public String generateDocumentNumber(@NotEmpty String documentationOfficeAbbrevi if (recycledId != null) return recycledId; DocumentNumberDTO documentNumberDTO = - repository - .findById(documentationOfficeAbbreviation) - .orElse( - DocumentNumberDTO.builder() - .documentationOfficeAbbreviation(documentationOfficeAbbreviation) - .lastNumber(0) - .build()); + getOrCreateDocumentNumberDTO(documentationOfficeAbbreviation); String documentNumber = DocumentNumberFormatter.builder() @@ -102,6 +96,26 @@ public String generateDocumentNumber(@NotEmpty String documentationOfficeAbbrevi return documentNumber; } + /** + * Retrieves an existing document number entry for the given documentation office abbreviation and + * year, or creates a new document number entry if none exists. + * + * @param documentationOfficeAbbreviation desired court abbreviation by office + * @return A {@link DocumentNumberDTO} containing the latest doc number count + */ + public DocumentNumberDTO getOrCreateDocumentNumberDTO( + @NotEmpty String documentationOfficeAbbreviation) { + return repository + .findByDocumentationOfficeAbbreviationAndYear( + documentationOfficeAbbreviation, DateUtil.getYear()) + .orElse( + DocumentNumberDTO.builder() + .documentationOfficeAbbreviation(documentationOfficeAbbreviation) + .lastNumber(0) + .year(DateUtil.getYear()) + .build()); + } + /** * Validate document number not exists in the database * diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java index 983d197e56..3d71090804 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java @@ -1,15 +1,13 @@ package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; -import jakarta.persistence.LockModeType; +import java.time.Year; import java.util.Optional; -import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Lock; import org.springframework.stereotype.Repository; @Repository public interface DatabaseDocumentNumberRepository extends JpaRepository { - @NotNull - @Lock(LockModeType.PESSIMISTIC_WRITE) - Optional findById(@NotNull String id); + + Optional findByDocumentationOfficeAbbreviationAndYear( + String documentationOfficeAbbreviation, Year year); } diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentNumberDTO.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentNumberDTO.java index 240d65e480..f0116708d9 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentNumberDTO.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DocumentNumberDTO.java @@ -2,13 +2,17 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.validation.constraints.NotEmpty; +import java.time.Year; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import org.jetbrains.annotations.NotNull; @Data @Builder @@ -18,7 +22,8 @@ @Table(name = "document_number", schema = "public") public class DocumentNumberDTO { - @Id + @Id @GeneratedValue private UUID id; + @Column(name = "documentation_office_abbreviation") @NotEmpty private String documentationOfficeAbbreviation; @@ -26,6 +31,10 @@ public class DocumentNumberDTO { @Column(name = "last_number") private int lastNumber; + @Column(name = "year") + @NotNull + private Year year; + public Integer increaseLastNumber() { this.lastNumber = lastNumber + 1; return lastNumber; diff --git a/backend/src/main/resources/db-scripts/migration/V1.11__add_id_to_document_number.sql b/backend/src/main/resources/db-scripts/migration/V1.11__add_id_to_document_number.sql new file mode 100644 index 0000000000..35c4259145 --- /dev/null +++ b/backend/src/main/resources/db-scripts/migration/V1.11__add_id_to_document_number.sql @@ -0,0 +1,13 @@ +-- Adding unique id to document number table +ALTER TABLE document_number + ADD COLUMN id UUID NOT NULL DEFAULT gen_random_uuid(); + +-- Drop the existing primary key constraint directing to documentation_office_abbreviation +ALTER TABLE document_number +DROP CONSTRAINT document_number_pkey; + +-- Add a new primary key constraint on id column +ALTER TABLE document_number + ADD CONSTRAINT document_number_pkey PRIMARY KEY (id); + + diff --git a/backend/src/main/resources/db-scripts/migration/V1.12__add_year_to_document_number.sql b/backend/src/main/resources/db-scripts/migration/V1.12__add_year_to_document_number.sql new file mode 100644 index 0000000000..ad3b645c0d --- /dev/null +++ b/backend/src/main/resources/db-scripts/migration/V1.12__add_year_to_document_number.sql @@ -0,0 +1,13 @@ +-- Adding year to document number count +ALTER TABLE IF EXISTS document_number + ADD year INTEGER; + +-- Adding previous year to document number count +UPDATE document_number SET year = 2024 WHERE year IS NULL; + +-- Setting required year for upcoming entries +ALTER TABLE document_number ALTER COLUMN year SET NOT NULL; + +-- Create a combined index on 'documentation_office_abbreviation' and 'year' +CREATE INDEX idx_documentation_office_abbreviation_year + ON document_number (documentation_office_abbreviation, year); diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorServiceTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorServiceTest.java index 9262021344..c97ccb7507 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorServiceTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/adapter/DatabaseDocumentNumberGeneratorServiceTest.java @@ -1,10 +1,12 @@ package de.bund.digitalservice.ris.caselaw.adapter; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseDocumentNumberRepository; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DatabaseDocumentationUnitRepository; +import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentNumberDTO; import de.bund.digitalservice.ris.caselaw.adapter.database.jpa.DocumentationUnitDTO; import de.bund.digitalservice.ris.caselaw.domain.DateUtil; import de.bund.digitalservice.ris.caselaw.domain.DocumentNumberRecyclingService; @@ -101,4 +103,41 @@ void shouldStopTrying_ifPatternIsInvalid() { .hasMessageContaining( "Could not " + "find pattern for abbreviation " + docOfficeAbbreviation); } + + @Test + void getOrCreateDocumentNumberDTO_startsByZero_ifYearAndAbbreviationDoNotExist() { + + var documentNumberDto = service.getOrCreateDocumentNumberDTO(DEFAULT_ABBREVIATION); + + Assertions.assertEquals(0, documentNumberDto.getLastNumber()); + Assertions.assertEquals(DateUtil.getYear(), documentNumberDto.getYear()); + Assertions.assertEquals( + DEFAULT_ABBREVIATION, documentNumberDto.getDocumentationOfficeAbbreviation()); + + verify(databaseDocumentNumberRepository) + .findByDocumentationOfficeAbbreviationAndYear(DEFAULT_ABBREVIATION, DateUtil.getYear()); + } + + @Test + void getOrCreateDocumentNumberDTO_shouldContinuesFromLast_ifYearAndAbbreviationExist() { + when(databaseDocumentNumberRepository.findByDocumentationOfficeAbbreviationAndYear( + DEFAULT_ABBREVIATION, DateUtil.getYear())) + .thenReturn( + Optional.of( + DocumentNumberDTO.builder() + .lastNumber(4) + .year(DateUtil.getYear()) + .documentationOfficeAbbreviation(DEFAULT_ABBREVIATION) + .build())); + + var documentNumberDto = service.getOrCreateDocumentNumberDTO(DEFAULT_ABBREVIATION); + + Assertions.assertEquals(4, documentNumberDto.getLastNumber()); + Assertions.assertEquals(DateUtil.getYear(), documentNumberDto.getYear()); + Assertions.assertEquals( + DEFAULT_ABBREVIATION, documentNumberDto.getDocumentationOfficeAbbreviation()); + + verify(databaseDocumentNumberRepository) + .findByDocumentationOfficeAbbreviationAndYear(DEFAULT_ABBREVIATION, DateUtil.getYear()); + } } From 5c02642ffac11e5b02d1f595396b56a3e8a8f313 Mon Sep 17 00:00:00 2001 From: nbabai Date: Mon, 6 Jan 2025 12:16:21 +0100 Subject: [PATCH 2/3] Fixing mock by doc office and year RISDEV-6004 --- .../tests/DocumentationUnitIntegrationTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentationUnitIntegrationTest.java b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentationUnitIntegrationTest.java index 323396292f..dc31143602 100644 --- a/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentationUnitIntegrationTest.java +++ b/backend/src/test/java/de/bund/digitalservice/ris/caselaw/integration/tests/DocumentationUnitIntegrationTest.java @@ -54,6 +54,7 @@ import de.bund.digitalservice.ris.caselaw.domain.AuthService; import de.bund.digitalservice.ris.caselaw.domain.ContentRelatedIndexing; import de.bund.digitalservice.ris.caselaw.domain.CoreData; +import de.bund.digitalservice.ris.caselaw.domain.DateUtil; import de.bund.digitalservice.ris.caselaw.domain.DocumentationOffice; import de.bund.digitalservice.ris.caselaw.domain.DocumentationUnit; import de.bund.digitalservice.ris.caselaw.domain.DocumentationUnitCreationParameters; @@ -77,8 +78,6 @@ import de.bund.digitalservice.ris.caselaw.webtestclient.RisWebTestClient; import java.net.URI; import java.time.LocalDate; -import java.time.Year; -import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -1036,18 +1035,20 @@ void testDeleteByUuid_withExistingReference_shouldNotRecycleDocumentNumberAfterF DeletedDocumentationUnitDTO.builder() .abbreviation("DS") .documentNumber("ZZRE202400001") - .year(Year.of(LocalDate.now().get(ChronoField.YEAR))) + .year(DateUtil.getYear()) .build(); deletedDocumentationIdsRepository.save(deletedDocumentationUnitDTO); when(documentNumberPatternConfig.getDocumentNumberPatterns()) .thenReturn(Map.of("DS", "ZZREYYYY*****")); - when(databaseDocumentNumberRepository.findById("DS")) + when(databaseDocumentNumberRepository.findByDocumentationOfficeAbbreviationAndYear( + "DS", DateUtil.getYear())) .thenReturn( Optional.of( DocumentNumberDTO.builder() .documentationOfficeAbbreviation("DS") .lastNumber(1) + .year(DateUtil.getYear()) .build())); risWebTestClient From f54336160391eb4dadceff54710cd8bed0a89289 Mon Sep 17 00:00:00 2001 From: nbabai Date: Mon, 6 Jan 2025 13:59:33 +0100 Subject: [PATCH 3/3] Only one thread can write lock RISDEV-6004 --- .../database/jpa/DatabaseDocumentNumberRepository.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java index 3d71090804..b1dab9fb85 100644 --- a/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java +++ b/backend/src/main/java/de/bund/digitalservice/ris/caselaw/adapter/database/jpa/DatabaseDocumentNumberRepository.java @@ -1,13 +1,19 @@ package de.bund.digitalservice.ris.caselaw.adapter.database.jpa; +import jakarta.persistence.LockModeType; +import jakarta.validation.constraints.NotBlank; import java.time.Year; import java.util.Optional; +import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; import org.springframework.stereotype.Repository; @Repository public interface DatabaseDocumentNumberRepository extends JpaRepository { + @NotNull + @Lock(LockModeType.PESSIMISTIC_WRITE) Optional findByDocumentationOfficeAbbreviationAndYear( - String documentationOfficeAbbreviation, Year year); + @NotBlank String documentationOfficeAbbreviation, @NotNull Year year); }