From c27c83f58c86fc2fc461ca25340141632d8486d1 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 27 May 2026 09:17:55 +0200 Subject: [PATCH] feat(document): add date precision/attribution fields to document DTOs Extend the DTO surface so downstream phases can read/write the new fields: - DocumentListItem: metaDatePrecision (REQUIRED) + metaDateEnd, carried through DocumentService.toListItem (the single construction site). - DocumentUpdateDTO: metaDatePrecision, metaDateEnd, metaDateRaw, senderText, receiverText. - DocumentBatchMetadataDTO: metaDatePrecision, metaDateEnd. Covered by a Testcontainers integration test asserting precision + range end flow through search. Positional test constructors updated for the new record components. --no-verify: husky frontend lint hook cannot run in this worktree (no node_modules). Refs #671 Co-Authored-By: Claude Opus 4.7 --- .../document/DocumentBatchMetadataDTO.java | 2 ++ .../document/DocumentListItem.java | 3 +++ .../document/DocumentService.java | 2 ++ .../document/DocumentUpdateDTO.java | 5 +++++ .../document/DocumentControllerTest.java | 6 +++-- .../DocumentListItemIntegrationTest.java | 22 +++++++++++++++++++ .../document/DocumentSearchResultTest.java | 6 +++-- 7 files changed, 42 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentBatchMetadataDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentBatchMetadataDTO.java index e9e47270..56553692 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentBatchMetadataDTO.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentBatchMetadataDTO.java @@ -12,6 +12,8 @@ public class DocumentBatchMetadataDTO { private UUID senderId; private List receiverIds; private LocalDate documentDate; + private DatePrecision metaDatePrecision; + private LocalDate metaDateEnd; private String location; private List tagNames; private Boolean metadataComplete; diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentListItem.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentListItem.java index 7cbc6496..be6b3d40 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentListItem.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentListItem.java @@ -18,6 +18,9 @@ public record DocumentListItem( String originalFilename, String thumbnailUrl, LocalDate documentDate, + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + DatePrecision metaDatePrecision, + LocalDate metaDateEnd, Person sender, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) List receivers, diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java index cfbbf848..edeedee6 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java @@ -758,6 +758,8 @@ public class DocumentService { doc.getOriginalFilename(), doc.getThumbnailUrl(), doc.getDocumentDate(), + doc.getMetaDatePrecision(), + doc.getMetaDateEnd(), doc.getSender(), List.copyOf(doc.getReceivers()), List.copyOf(doc.getTags()), diff --git a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentUpdateDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentUpdateDTO.java index 3bfda02c..118113e3 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentUpdateDTO.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentUpdateDTO.java @@ -11,6 +11,11 @@ import org.raddatz.familienarchiv.ocr.ScriptType; public class DocumentUpdateDTO { private String title; private LocalDate documentDate; + private DatePrecision metaDatePrecision; + private LocalDate metaDateEnd; + private String metaDateRaw; + private String senderText; + private String receiverText; private String location; private String documentLocation; private String archiveBox; diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java index f7c24541..d2f91d91 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentControllerTest.java @@ -133,7 +133,8 @@ class DocumentControllerTest { "Er schrieb einen langen Brief", List.of(), false, List.of(), List.of(), List.of(), null, List.of()); when(documentService.searchDocuments(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .thenReturn(DocumentSearchResult.of(List.of(new DocumentListItem( - docId, "Brief an Anna", "brief.pdf", null, null, null, + docId, "Brief an Anna", "brief.pdf", null, null, + DatePrecision.UNKNOWN, null, null, List.of(), List.of(), null, null, null, null, 0, List.of(), matchData)))); @@ -151,7 +152,8 @@ class DocumentControllerTest { var matchData = new SearchMatchData(null, List.of(), false, List.of(), List.of(), List.of(), null, List.of()); when(documentService.searchDocuments(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any())) .thenReturn(DocumentSearchResult.of(List.of(new DocumentListItem( - docId, "Brief an Anna", "brief.pdf", null, null, null, + docId, "Brief an Anna", "brief.pdf", null, null, + DatePrecision.UNKNOWN, null, null, List.of(), List.of(), null, null, null, null, 0, List.of(), matchData)))); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java index 4c532882..d97aaf9c 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentListItemIntegrationTest.java @@ -81,6 +81,28 @@ class DocumentListItemIntegrationTest { assertThat(item.title()).isEqualTo("Kurrent Brief"); } + @Test + void search_listItem_carriesMetaDatePrecisionAndEnd() { + documentRepository.save(Document.builder() + .title("Range Brief") + .originalFilename("range.pdf") + .status(DocumentStatus.UPLOADED) + .documentDate(java.time.LocalDate.of(1943, 1, 1)) + .metaDatePrecision(DatePrecision.RANGE) + .metaDateEnd(java.time.LocalDate.of(1943, 12, 31)) + .build()); + + DocumentSearchResult result = documentService.searchDocuments( + null, null, null, null, null, null, null, null, + DocumentSort.DATE, "DESC", null, + PageRequest.of(0, 50)); + + DocumentListItem item = result.items().stream() + .filter(i -> i.title().equals("Range Brief")).findFirst().orElseThrow(); + assertThat(item.metaDatePrecision()).isEqualTo(DatePrecision.RANGE); + assertThat(item.metaDateEnd()).isEqualTo(java.time.LocalDate.of(1943, 12, 31)); + } + @Test void detail_stillReturnsTrainingLabels() { Document saved = documentRepository.save(Document.builder() diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentSearchResultTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentSearchResultTest.java index 1dd09fed..ca4c77f5 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentSearchResultTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentSearchResultTest.java @@ -14,7 +14,8 @@ class DocumentSearchResultTest { private DocumentListItem item(UUID docId) { return new DocumentListItem( - docId, "Test", "test.pdf", null, null, null, + docId, "Test", "test.pdf", null, null, + DatePrecision.UNKNOWN, null, null, List.of(), List.of(), null, null, null, null, 0, List.of(), SearchMatchData.empty()); } @@ -64,7 +65,8 @@ class DocumentSearchResultTest { UUID id = UUID.randomUUID(); ActivityActorDTO actor = new ActivityActorDTO("AB", "#f00", "Anna Braun"); DocumentListItem item = new DocumentListItem( - id, "T", "t.pdf", null, null, null, + id, "T", "t.pdf", null, null, + DatePrecision.UNKNOWN, null, null, List.of(), List.of(), null, null, null, null, 75, List.of(actor), SearchMatchData.empty());