refactor(document): extract title composition into shared DocumentTitleFactory (#726)
Move DocumentTitleFormatter from importing into the document package and
introduce DocumentTitleFactory there as the single source of truth for the
{index} – {dateLabel} – {location} formula. DocumentImporter now consumes the
factory instead of owning the composition; the document package owns the rule,
importing depends on it (not the reverse). No behavioral change — importer
title assertions and the #666 fixture parity test stay green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package org.raddatz.familienarchiv.document;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Single source of truth for the auto-generated document title
|
||||
* {@code {index} – {dateLabel} – {location}}.
|
||||
*
|
||||
* <p>The {@code document} package owns this formula; {@code importing} consumes it
|
||||
* (see ADR for issue #726). The leading {@code index} is the document's
|
||||
* {@code originalFilename}; the date label is the honest German label produced by
|
||||
* {@link DocumentTitleFormatter} (the Java half of the #666 date-label split); the
|
||||
* trailing location is the {@code meta_location} verbatim, omitted when blank.
|
||||
*/
|
||||
@Component
|
||||
public class DocumentTitleFactory {
|
||||
|
||||
static final String SEPARATOR = " – ";
|
||||
|
||||
/**
|
||||
* Composes the auto-title from the document's current state. The date segment is
|
||||
* dropped for UNKNOWN precision or a null date (the honest "no date" case); the
|
||||
* location segment is dropped when blank.
|
||||
*/
|
||||
public String build(Document doc) {
|
||||
StringBuilder title = new StringBuilder(doc.getOriginalFilename());
|
||||
if (doc.getDocumentDate() != null && doc.getMetaDatePrecision() != DatePrecision.UNKNOWN) {
|
||||
title.append(SEPARATOR).append(DocumentTitleFormatter.formatTitleDate(
|
||||
doc.getDocumentDate(), doc.getMetaDatePrecision(),
|
||||
doc.getMetaDateEnd(), doc.getMetaDateRaw()));
|
||||
}
|
||||
if (doc.getLocation() != null && !doc.getLocation().isBlank()) {
|
||||
title.append(SEPARATOR).append(doc.getLocation());
|
||||
}
|
||||
return title.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
package org.raddatz.familienarchiv.importing;
|
||||
|
||||
import org.raddatz.familienarchiv.document.DatePrecision;
|
||||
package org.raddatz.familienarchiv.document;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.raddatz.familienarchiv.document.DatePrecision;
|
||||
import org.raddatz.familienarchiv.document.Document;
|
||||
import org.raddatz.familienarchiv.document.DocumentService;
|
||||
import org.raddatz.familienarchiv.document.DocumentTitleFactory;
|
||||
import org.raddatz.familienarchiv.document.DocumentStatus;
|
||||
import org.raddatz.familienarchiv.document.ThumbnailAsyncRunner;
|
||||
import org.raddatz.familienarchiv.exception.DomainException;
|
||||
@@ -74,6 +75,7 @@ public class DocumentImporter {
|
||||
Pattern.compile("[A-Za-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF]{1,4}-+\\d+x?");
|
||||
|
||||
private final DocumentService documentService;
|
||||
private final DocumentTitleFactory documentTitleFactory;
|
||||
private final PersonService personService;
|
||||
private final TagService tagService;
|
||||
private final S3Client s3Client;
|
||||
@@ -181,7 +183,7 @@ public class DocumentImporter {
|
||||
applyAttribution(doc, row);
|
||||
applyDates(doc, row);
|
||||
applyAuthoritativeAssociations(doc, row);
|
||||
applyFileMetadata(doc, s3Key, contentType, status, index);
|
||||
applyFileMetadata(doc, s3Key, contentType, status);
|
||||
applyComputedFlags(doc);
|
||||
return doc;
|
||||
}
|
||||
@@ -217,14 +219,15 @@ public class DocumentImporter {
|
||||
attachTag(doc, row.get("tags"));
|
||||
}
|
||||
|
||||
// S3 key, content type, status, and the index-derived title.
|
||||
// S3 key, content type, status, and the index-derived title. The title formula lives in
|
||||
// the document package's DocumentTitleFactory (single source of truth, #726); by this point
|
||||
// applyDates has populated the date/location and originalFilename carries the index.
|
||||
private void applyFileMetadata(Document doc, String s3Key, String contentType,
|
||||
DocumentStatus status, String index) {
|
||||
DocumentStatus status) {
|
||||
doc.setStatus(status);
|
||||
doc.setFilePath(s3Key);
|
||||
doc.setContentType(contentType);
|
||||
doc.setTitle(buildTitle(index, doc.getDocumentDate(), doc.getMetaDatePrecision(),
|
||||
doc.getMetaDateEnd(), doc.getMetaDateRaw(), doc.getLocation()));
|
||||
doc.setTitle(documentTitleFactory.build(doc));
|
||||
}
|
||||
|
||||
// metadataComplete: a document counts as fully described if any of the three "who/when"
|
||||
@@ -235,20 +238,6 @@ public class DocumentImporter {
|
||||
|| !doc.getReceivers().isEmpty());
|
||||
}
|
||||
|
||||
// The title carries the date at the HONEST precision (never a fabricated day) via the
|
||||
// shared DocumentTitleFormatter, plus the location — kept under 20 lines by delegating.
|
||||
private static String buildTitle(String index, LocalDate date, DatePrecision precision,
|
||||
LocalDate end, String raw, String location) {
|
||||
StringBuilder title = new StringBuilder(index);
|
||||
if (date != null && precision != DatePrecision.UNKNOWN) {
|
||||
title.append(" – ").append(DocumentTitleFormatter.formatTitleDate(date, precision, end, raw));
|
||||
}
|
||||
if (location != null && !location.isBlank()) {
|
||||
title.append(" – ").append(location);
|
||||
}
|
||||
return title.toString();
|
||||
}
|
||||
|
||||
// ─── attribution routing — register-first, always retain raw ─────────────────────
|
||||
|
||||
private Person resolveSender(String slug, String rawName) {
|
||||
|
||||
Reference in New Issue
Block a user