diff --git a/backend/src/lib/date-precision.ts b/backend/src/lib/date-precision.ts new file mode 100644 index 0000000..6efe191 --- /dev/null +++ b/backend/src/lib/date-precision.ts @@ -0,0 +1,26 @@ +// Une date corrigée à la main perd la précision lue dans le GEDCOM (« vers 1879 », « entre 1914 et 1918 ») : +// elle décrivait l'ancienne date, pas celle qu'on vient de saisir. Une date renvoyée telle quelle la garde. + +const sameDay = (a: Date | null | undefined, b: Date | null | undefined) => + (a ? a.toISOString().slice(0, 10) : null) === (b ? b.toISOString().slice(0, 10) : null) + +type PersonDates = { birthDate: Date | null; deathDate: Date | null } + +export function personPrecisionResets(current: PersonDates | null, next: Partial) { + const out: { birthDatePrecision?: null; birthDateEnd?: null; deathDatePrecision?: null; deathDateEnd?: null } = {} + if (!current) return out + if (next.birthDate !== undefined && !sameDay(current.birthDate, next.birthDate)) { + out.birthDatePrecision = null + out.birthDateEnd = null + } + if (next.deathDate !== undefined && !sameDay(current.deathDate, next.deathDate)) { + out.deathDatePrecision = null + out.deathDateEnd = null + } + return out +} + +export function unionPrecisionResets(current: { startDate: Date | null } | null, next: { startDate?: Date | null }) { + if (!current || next.startDate === undefined || sameDay(current.startDate, next.startDate)) return {} + return { startDatePrecision: null } +} diff --git a/backend/src/routes/contributions.ts b/backend/src/routes/contributions.ts index b5b8fdc..17ed7bf 100644 --- a/backend/src/routes/contributions.ts +++ b/backend/src/routes/contributions.ts @@ -12,6 +12,7 @@ import { syncPersonMediaOrder } from '../lib/media-order.js' import type { Actor, MembershipRole } from '../types/auth.js' import { findParentChildValidationError, findUnionValidationError } from '../utils/relationship-guards.js' import { nextSiblingOrder } from '../lib/sibling-order.js' +import { personPrecisionResets, unionPrecisionResets } from '../lib/date-precision.js' const isoDateRegex = /^\d{4}-\d{2}-\d{2}$/ @@ -391,6 +392,17 @@ async function applyContributionChange( throw new ContributionRouteError(400, 'invalid_change_after') } + const nextDates = { + birthDate: parsed.data.birthDate !== undefined ? toDateOrNull(parsed.data.birthDate) : undefined, + deathDate: parsed.data.deathDate !== undefined ? toDateOrNull(parsed.data.deathDate) : undefined, + } + const currentDates = nextDates.birthDate !== undefined || nextDates.deathDate !== undefined + ? await tx.person.findFirst({ + where: { id: change.entityId, treeId, deletedAt: null }, + select: { birthDate: true, deathDate: true }, + }) + : null + const result = await tx.person.updateMany({ where: { id: change.entityId, @@ -398,11 +410,12 @@ async function applyContributionChange( deletedAt: null, }, data: { + ...personPrecisionResets(currentDates, nextDates), firstName: parsed.data.firstName !== undefined ? parsed.data.firstName.trim() : undefined, lastName: parsed.data.lastName !== undefined ? parsed.data.lastName.trim() : undefined, birthName: parsed.data.birthName !== undefined ? toTrimmedStringOrNull(parsed.data.birthName) : undefined, - birthDate: parsed.data.birthDate !== undefined ? toDateOrNull(parsed.data.birthDate) : undefined, - deathDate: parsed.data.deathDate !== undefined ? toDateOrNull(parsed.data.deathDate) : undefined, + birthDate: nextDates.birthDate, + deathDate: nextDates.deathDate, birthPlace: parsed.data.birthPlace !== undefined ? toTrimmedStringOrNull(parsed.data.birthPlace) : undefined, profession: parsed.data.profession !== undefined ? toTrimmedStringOrNull(parsed.data.profession) : undefined, region: parsed.data.region !== undefined ? toTrimmedStringOrNull(parsed.data.region) : undefined, @@ -511,6 +524,7 @@ async function applyContributionChange( select: { partner1PersonId: true, partner2PersonId: true, + startDate: true, }, }) if (!existingUnion) { @@ -537,6 +551,9 @@ async function applyContributionChange( deletedAt: null, }, data: { + ...unionPrecisionResets(existingUnion, { + startDate: parsed.data.startDate !== undefined ? toDateOrNull(parsed.data.startDate) : undefined, + }), partner1PersonId: parsed.data.partner1PersonId, partner2PersonId: parsed.data.partner2PersonId, unionType: parsed.data.unionType, diff --git a/backend/src/routes/trees.ts b/backend/src/routes/trees.ts index ac43ec7..86c8eb2 100644 --- a/backend/src/routes/trees.ts +++ b/backend/src/routes/trees.ts @@ -15,6 +15,7 @@ import { nextSiblingOrder } from '../lib/sibling-order.js' import { reserveTreeUnlockAttempt, serializePasswordCheck } from '../lib/unlock-guard.js' import type { MembershipRole } from '../types/auth.js' import { findParentChildValidationError, findUnionValidationError } from '../utils/relationship-guards.js' +import { personPrecisionResets, unionPrecisionResets } from '../lib/date-precision.js' const MAX_TREES_PER_ACCOUNT = 10 @@ -1371,13 +1372,21 @@ export const treeRoutes: FastifyPluginAsync = async (app) => { data.updatedBy = user.userId + const datesChanged = data.birthDate !== undefined || data.deathDate !== undefined + const current = datesChanged + ? await app.prisma.person.findFirst({ + where: { id: params.data.personId, treeId: params.data.id, deletedAt: null }, + select: { birthDate: true, deathDate: true }, + }) + : null + const updated = await app.prisma.person.updateMany({ where: { id: params.data.personId, treeId: params.data.id, deletedAt: null, }, - data, + data: { ...data, ...personPrecisionResets(current, data) }, }) if (updated.count === 0) { @@ -1586,6 +1595,7 @@ export const treeRoutes: FastifyPluginAsync = async (app) => { select: { partner1PersonId: true, partner2PersonId: true, + startDate: true, }, }) @@ -1636,6 +1646,7 @@ export const treeRoutes: FastifyPluginAsync = async (app) => { return reply.code(400).send({ error: unionValidationError }) } + const startDate = payload.data.startDate !== undefined ? toDateOrNull(payload.data.startDate) : undefined const updated = await app.prisma.union.updateMany({ where: { id: params.data.unionId, @@ -1643,10 +1654,11 @@ export const treeRoutes: FastifyPluginAsync = async (app) => { deletedAt: null, }, data: { + ...unionPrecisionResets(existingUnion, { startDate }), partner1PersonId: payload.data.partner1PersonId, partner2PersonId: payload.data.partner2PersonId, unionType: payload.data.unionType, - startDate: payload.data.startDate !== undefined ? toDateOrNull(payload.data.startDate) : undefined, + startDate, endDate: payload.data.endDate !== undefined ? toDateOrNull(payload.data.endDate) : undefined, displayOrder: payload.data.displayOrder, }, diff --git a/backend/test/date-precision.test.ts b/backend/test/date-precision.test.ts new file mode 100644 index 0000000..ac348d0 --- /dev/null +++ b/backend/test/date-precision.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest' + +import { personPrecisionResets, unionPrecisionResets } from '../src/lib/date-precision' + +const d = (iso: string) => new Date(`${iso}T00:00:00.000Z`) + +describe('précision d’une date corrigée à la main', () => { + const current = { birthDate: d('1879-01-01'), deathDate: d('1950-05-02') } + + it('une date changée perd sa précision, une date renvoyée telle quelle la garde', () => { + expect(personPrecisionResets(current, { birthDate: d('1880-03-12'), deathDate: d('1950-05-02') })) + .toEqual({ birthDatePrecision: null, birthDateEnd: null }) + expect(personPrecisionResets(current, { birthDate: d('1879-01-01') })).toEqual({}) + expect(personPrecisionResets(current, { deathDate: null })).toEqual({ deathDatePrecision: null, deathDateEnd: null }) + expect(personPrecisionResets(current, {})).toEqual({}) + }) + + it('mariage', () => { + expect(unionPrecisionResets({ startDate: d('1905-01-01') }, { startDate: d('1906-01-01') })).toEqual({ startDatePrecision: null }) + expect(unionPrecisionResets({ startDate: d('1905-01-01') }, { startDate: d('1905-01-01') })).toEqual({}) + expect(unionPrecisionResets({ startDate: d('1905-01-01') }, {})).toEqual({}) + }) +})