feat(search): add searchDocumentsByPersonId with Specification-based sender/receiver query
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,7 @@ public interface DocumentRepository extends JpaRepository<Document, UUID>, JpaSp
|
|||||||
@EntityGraph("Document.full")
|
@EntityGraph("Document.full")
|
||||||
List<Document> findByReceiversId(UUID receiverId);
|
List<Document> findByReceiversId(UUID receiverId);
|
||||||
|
|
||||||
|
|
||||||
// Callers access only doc.getTags() to mutate the set — receivers/sender not touched; no graph needed.
|
// Callers access only doc.getTags() to mutate the set — receivers/sender not touched; no graph needed.
|
||||||
List<Document> findByTags_Id(UUID tagId);
|
List<Document> findByTags_Id(UUID tagId);
|
||||||
|
|
||||||
|
|||||||
@@ -1034,7 +1034,25 @@ public class DocumentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DocumentSearchResult searchDocumentsByPersonId(UUID personId, LocalDate from, LocalDate to, Pageable pageable) {
|
public DocumentSearchResult searchDocumentsByPersonId(UUID personId, LocalDate from, LocalDate to, Pageable pageable) {
|
||||||
throw new UnsupportedOperationException("Implemented in Task 12 — findBySenderOrReceiver JPQL");
|
Person person = personService.getById(personId);
|
||||||
|
Specification<Document> spec = buildPersonSpec(person, from, to);
|
||||||
|
Page<Document> page = documentRepository.findAll(spec, pageable);
|
||||||
|
List<DocumentListItem> items = enrichItems(page.getContent(), null);
|
||||||
|
return DocumentSearchResult.paged(items, pageable, page.getTotalElements());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Specification<Document> buildPersonSpec(Person person, LocalDate from, LocalDate to) {
|
||||||
|
return (root, query, cb) -> {
|
||||||
|
if (query != null) query.distinct(true);
|
||||||
|
var receiversJoin = root.join("receivers", jakarta.persistence.criteria.JoinType.LEFT);
|
||||||
|
var senderPredicate = cb.equal(root.get("sender"), person);
|
||||||
|
var receiverPredicate = cb.equal(receiversJoin, person);
|
||||||
|
var personPredicate = cb.or(senderPredicate, receiverPredicate);
|
||||||
|
var predicates = new java.util.ArrayList<>(java.util.List.of(personPredicate));
|
||||||
|
if (from != null) predicates.add(cb.greaterThanOrEqualTo(root.get("documentDate"), from));
|
||||||
|
if (to != null) predicates.add(cb.lessThanOrEqualTo(root.get("documentDate"), to));
|
||||||
|
return cb.and(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getIncompleteCount() {
|
public long getIncompleteCount() {
|
||||||
|
|||||||
@@ -624,4 +624,88 @@ class DocumentRepositoryTest {
|
|||||||
.reviewed(reviewed)
|
.reviewed(reviewed)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── searchDocumentsByPersonId (via Specification) ───────────────────────
|
||||||
|
|
||||||
|
private Page<Document> searchByPerson(Person person, LocalDate from, LocalDate to) {
|
||||||
|
Specification<Document> spec = (root, query, cb) -> {
|
||||||
|
if (query != null) query.distinct(true);
|
||||||
|
var receiversJoin = root.join("receivers", jakarta.persistence.criteria.JoinType.LEFT);
|
||||||
|
var personPredicate = cb.or(
|
||||||
|
cb.equal(root.get("sender"), person),
|
||||||
|
cb.equal(receiversJoin, person));
|
||||||
|
var predicates = new java.util.ArrayList<>(java.util.List.of(personPredicate));
|
||||||
|
if (from != null) predicates.add(cb.greaterThanOrEqualTo(root.get("documentDate"), from));
|
||||||
|
if (to != null) predicates.add(cb.lessThanOrEqualTo(root.get("documentDate"), to));
|
||||||
|
return cb.and(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
|
||||||
|
};
|
||||||
|
return documentRepository.findAll(spec, PageRequest.of(0, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void searchByPersonSpec_returnsDocument_whenPersonIsSender() {
|
||||||
|
Person person = personRepository.save(Person.builder().lastName("Raddatz").build());
|
||||||
|
Document doc = documentRepository.save(Document.builder()
|
||||||
|
.title("Senderbrief").originalFilename("sender.pdf")
|
||||||
|
.status(DocumentStatus.UPLOADED).sender(person).build());
|
||||||
|
|
||||||
|
Page<Document> result = searchByPerson(person, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getContent()).extracting(Document::getId).containsExactly(doc.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void searchByPersonSpec_returnsDocument_whenPersonIsReceiver() {
|
||||||
|
Person person = personRepository.save(Person.builder().lastName("Raddatz").build());
|
||||||
|
Document doc = documentRepository.save(Document.builder()
|
||||||
|
.title("Empfängerbrief").originalFilename("receiver.pdf")
|
||||||
|
.status(DocumentStatus.UPLOADED)
|
||||||
|
.receivers(new java.util.HashSet<>(List.of(person))).build());
|
||||||
|
|
||||||
|
Page<Document> result = searchByPerson(person, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getContent()).extracting(Document::getId).containsExactly(doc.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void searchByPersonSpec_returnsDocumentOnce_whenPersonIsBothSenderAndReceiver() {
|
||||||
|
Person person = personRepository.save(Person.builder().lastName("Raddatz").build());
|
||||||
|
Document doc = documentRepository.save(Document.builder()
|
||||||
|
.title("SenderEmpfänger").originalFilename("both.pdf")
|
||||||
|
.status(DocumentStatus.UPLOADED).sender(person)
|
||||||
|
.receivers(new java.util.HashSet<>(List.of(person))).build());
|
||||||
|
|
||||||
|
Page<Document> result = searchByPerson(person, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getContent()).hasSize(1);
|
||||||
|
assertThat(result.getContent().get(0).getId()).isEqualTo(doc.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void searchByPersonSpec_excludesDocuments_outsideDateRange() {
|
||||||
|
Person person = personRepository.save(Person.builder().lastName("Raddatz").build());
|
||||||
|
Document inside = documentRepository.save(Document.builder()
|
||||||
|
.title("Innen").originalFilename("inside.pdf").status(DocumentStatus.UPLOADED)
|
||||||
|
.sender(person).documentDate(LocalDate.of(1918, 6, 15)).build());
|
||||||
|
documentRepository.save(Document.builder()
|
||||||
|
.title("Außen").originalFilename("outside.pdf").status(DocumentStatus.UPLOADED)
|
||||||
|
.sender(person).documentDate(LocalDate.of(1920, 1, 1)).build());
|
||||||
|
|
||||||
|
Page<Document> result = searchByPerson(person, LocalDate.of(1914, 1, 1), LocalDate.of(1918, 12, 31));
|
||||||
|
|
||||||
|
assertThat(result.getContent()).extracting(Document::getId).containsExactly(inside.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void searchByPersonSpec_returnsEmpty_whenNoMatchingDocuments() {
|
||||||
|
Person person = personRepository.save(Person.builder().lastName("Raddatz").build());
|
||||||
|
Person other = personRepository.save(Person.builder().lastName("Braun").build());
|
||||||
|
documentRepository.save(Document.builder()
|
||||||
|
.title("Fremder Brief").originalFilename("other.pdf")
|
||||||
|
.status(DocumentStatus.UPLOADED).sender(other).build());
|
||||||
|
|
||||||
|
Page<Document> result = searchByPerson(person, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getContent()).isEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user