feat(person): migrate birth/death year to LocalDate + DatePrecision (#773) #812
Reference in New Issue
Block a user
Delete Branch "feat/issue-773-person-birth-death-localdate"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #773.
Implements the full spec from #773:
Person.birthYear/deathYear(Integer) becomebirthDate/deathDate(LocalDate) + NOT NULLDatePrecisioncolumns mirroringDocument.metaDatePrecision.Commits
feat(person): add BIRTH_AFTER_DEATH and INVALID_DATE_PRECISION error codes— backend enum + frontend mirror + i18n (de/en/es, incl. mixed-precision workaround hint)feat(person): V76 migration— pre-check abort gates,YYYY-01-01/YEARbackfill, named CHECK constraints, column drop; staged-Flyway Testcontainers tests (7)feat(person): store birth/death as LocalDate + DatePrecision— entity,PersonUpdateDTO,validateLifeDates,preferHumanDate(DatePrecisionPair), native queryEXTRACT(YEAR …)projections, null-safePersonNodeDTO/RelationshipDTOyearschore(api): regenerate TypeScript typesfeat(person): formatLifeDateRange takes date + precision— delegates toformatDocumentDate; new glyph-freeformatLifeDatehelperfeat(person): render precise life dates on cards, hover card, and mention dropdown— aria-hidden* / †glyphs, empty-state guards, 320px wrapfeat(person): date + precision controls on person new/edit forms— newPersonLifeDateField(German date input + hidden ISO + DAY/MONTH/YEAR select,min-h-[44px])docs(person): ADR-039, DB diagrams, V76 deploy runbook notetest(person): add now-required precision fields to Person test fixturesDeviations from the issue text (each deliberate)
badRequest()→ 400, notconflict()→ 409 forBIRTH_AFTER_DEATH/INVALID_DATE_PRECISION: the spec's code sample saidconflict(), but acceptance criteria #4/#10 require 400 and the document domain'sINVALID_DATE_RANGEusesbadRequest(). Confirmed with Marcel before implementation.chk_person_birth_before_death, which exists once.DeserializationFeaturehit insrc/mainisRestClientOcrClient's private ObjectMapper (OCR HTTP client, setsFAIL_ON_UNKNOWN_PROPERTIES=true) — the Spring MVC mapper has no enum-leniency override, so unknown enum values still 400. Documented inPersonControllerTest.formatLifeDateRangecaller found and updated:routes/persons/[id]/PersonCard.svelte(not listed in the issue).searchWithDocumentCounthad a GROUP BY among the four summary queries — updated to the four new columns; the other three never grouped.persons_tree.pyunchanged — it already emits year-only (_parse_year); its 57 tests pass untouched, matching the issue's test section.formatLifeDate) whileformatLifeDateRangekeeps glyphs for plain-text contexts (MentionDropdown subtitle) — the issue required both behaviors.Verification
PersonServiceTest(82),PersonControllerTest(68),PersonImportUpsertTest(15),PersonRepositoryTest(59, incl. 3 new projection tests),PersonServiceIntegrationTest(19), importer + relationship + canonical-import suites — all green;mvnw clean package -DskipTestsbuilds.personLifeDates.spec(21), card/hover/mention/form browser suites incl. 320px layout + APPROX render tests — all green;svelte-checkat 795 errors (below the ~834 pre-existing baseline; remainingbirthDatePrecisionmentions are in fixtures that were already type-broken before this branch).test_persons_tree.py57 passed, unchanged.Deploy
V76 is one-way. Take a manual
pg_dumpbefore deploying (no automated nightly backup exists yet — runbook note added todocs/DEPLOYMENT.md§5 with the targetedpg_restore -t personsrollback).🤖 Generated with Claude Code
Review findings addressed in 7 follow-up commits (f2f90065..2fac6873):
🔴 MentionDropdown life dates blank at runtime — fixed in two commits:
92672dbexposesbirthDate/birthDatePrecision/deathDate/deathDatePrecisionon thePersonSummaryDTOprojection and all four native queries (+4 Testcontainers projection tests, red→green).82af906retypes the mention dropdown/editor items asPersonSummaryDTO(the actual wire shape), regenerates the API types from a throwaway-DB backend run (dev DB stays at V74, untouched), and guards the twopersonIdconsumers against the schema-optionalid.🟡 Silent date clear on partial input —
36a7fd6:PersonLifeDateFieldnow delegates to the sharedDateInputprimitive (inlineform_date_error, calendar validation) and sets a custom validity while the error is present, so the browser blocks native submission on both person forms. Full clear stays submittable (intentional clear path). NewPersonLifeDateField.svelte.spec.ts(4 tests) + 1 edit-form test.🟡 Import 500 on birth>death conflict —
ef7d0f2: the canonical upsert resolves bothDatePrecisionPairs and, on conflict, keeps the stored life dates (empty for a new person), drops the canonical refresh, and WARNs with the sourceRef — REQ-IMP-001, never aborts the batch, hand-entered dates survive by construction. 3 new Mockito tests.Nits —
2a9ae80sharesyearOfbetween the relationship services,11efb44adds the trailing period toerror_invalid_date_precision(de/en/es),2fac687documents the YEAR seeding of legacy precisions in ADR-039.Verification:
PersonRepositoryTest(63),PersonControllerTest(68),PersonImportUpsertTest(18),PersonServiceTest(82), relationship suites (31) all green; mention/dropdown/editor + life-date-field + edit-form + DateInput + persons/new browser suites (147 tests across runs) all green; svelte-check clean for all touched files.🤖 Generated with Claude Code