From fbf4725e97ce13aa36f4e9ca19ba4e95c5304976 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 7 May 2026 21:50:05 +0200 Subject: [PATCH] feat(documents): add DocumentService.getDensity (#385) Maps the repository's Object[] rows into a DocumentDensityResult and pairs them with the archive-wide min/max meta_date range. Read-only, no @Transactional needed. Co-Authored-By: Claude Sonnet 4.6 --- .../document/DocumentService.java | 13 +++++ .../document/DocumentServiceTest.java | 52 +++++++++++++++++++ 2 files changed, 65 insertions(+) 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 8cef03a5..9d831380 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/document/DocumentService.java @@ -125,6 +125,19 @@ public class DocumentService { return titles; } + /** + * Per-month document counts for the timeline density widget (issue #385). + * Returns only months that have at least one document — the frontend fills + * gaps using the {@code minDate}/{@code maxDate} bounds. + */ + public DocumentDensityResult getDensity(LocalDate from, LocalDate to) { + List buckets = documentRepository.findDensityByMonth(from, to).stream() + .map(row -> new MonthBucket((String) row[0], ((Number) row[1]).intValue())) + .toList(); + DocumentDateRangeProjection range = documentRepository.findMinMaxDocumentDate(); + return new DocumentDensityResult(buckets, range.getMinDate(), range.getMaxDate()); + } + /** * Lädt eine Datei hoch. * - Prüft, ob ein Eintrag (aus Excel) schon existiert. diff --git a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java index e3390024..545118f2 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/document/DocumentServiceTest.java @@ -2321,4 +2321,56 @@ class DocumentServiceTest { assertThat(documentService.save(doc)).isEqualTo(doc); verify(documentRepository).save(doc); } + + // ─── getDensity ──────────────────────────────────────────────────────────── + + @Test + void getDensity_returnsEmptyResult_whenArchiveIsEmpty() { + when(documentRepository.findDensityByMonth(null, null)).thenReturn(List.of()); + DocumentDateRangeProjection emptyRange = mock(DocumentDateRangeProjection.class); + when(emptyRange.getMinDate()).thenReturn(null); + when(emptyRange.getMaxDate()).thenReturn(null); + when(documentRepository.findMinMaxDocumentDate()).thenReturn(emptyRange); + + DocumentDensityResult result = documentService.getDensity(null, null); + + assertThat(result.buckets()).isEmpty(); + assertThat(result.minDate()).isNull(); + assertThat(result.maxDate()).isNull(); + } + + @Test + void getDensity_mapsRepositoryRowsToMonthBuckets() { + List rows = List.of( + new Object[]{"1915-08", 2L}, + new Object[]{"1915-09", 1L} + ); + when(documentRepository.findDensityByMonth(null, null)).thenReturn(rows); + DocumentDateRangeProjection range = mock(DocumentDateRangeProjection.class); + when(range.getMinDate()).thenReturn(LocalDate.of(1915, 8, 3)); + when(range.getMaxDate()).thenReturn(LocalDate.of(1915, 9, 1)); + when(documentRepository.findMinMaxDocumentDate()).thenReturn(range); + + DocumentDensityResult result = documentService.getDensity(null, null); + + assertThat(result.buckets()).extracting(MonthBucket::month) + .containsExactly("1915-08", "1915-09"); + assertThat(result.buckets()).extracting(MonthBucket::count) + .containsExactly(2, 1); + assertThat(result.minDate()).isEqualTo(LocalDate.of(1915, 8, 3)); + assertThat(result.maxDate()).isEqualTo(LocalDate.of(1915, 9, 1)); + } + + @Test + void getDensity_passesFromAndToBoundsToRepository() { + when(documentRepository.findDensityByMonth(any(), any())).thenReturn(List.of()); + DocumentDateRangeProjection range = mock(DocumentDateRangeProjection.class); + when(documentRepository.findMinMaxDocumentDate()).thenReturn(range); + + LocalDate from = LocalDate.of(1914, 1, 1); + LocalDate to = LocalDate.of(1918, 12, 31); + documentService.getDensity(from, to); + + verify(documentRepository).findDensityByMonth(from, to); + } }