feat(vaccinations): carry the immunization log through export and restore

- a new backup section reads the dose and its document link through
  their own delegates and writes both ends in one file, the arrangement
  the visit section already uses
- the practitioner and the visit remap to the rows the same restore put
  back; the booster reminder never travels, so its reference resolves to
  null and the drop is reported rather than swallowed
- the antigen slug restores verbatim: a slug the catalogue has since
  dropped still says what a person was given
- the round trip seeds a dose with every optional column filled and
  reads each one back, so a returning row count cannot stand in for a
  returning record
This commit is contained in:
Marc-André Bombeck committed 2026-08-08 13:54:24 +02:00
1 parent 1bd97de943
commit 6926caa11d
18 files changed
+698 -3

No files matched your search

@@ -290,6 +290,8 @@ function sourceClient() {
encounterDocumentLink: { findMany: vi.fn().mockResolvedValue([]) },
encounterLabLink: { findMany: vi.fn().mockResolvedValue([]) },
encounterConditionLink: { findMany: vi.fn().mockResolvedValue([]) },
vaccinationRecord: { findMany: vi.fn().mockResolvedValue([]) },
vaccinationDocumentLink: { findMany: vi.fn().mockResolvedValue([]) },
customMetric: {
findMany: vi.fn().mockResolvedValue([
{
@@ -49,6 +49,7 @@ import { restoreProfileData } from "@/lib/export/profile-backup";
import { restoreIntradayProfileData } from "@/lib/export/intraday-profile-backup";
import { restoreHealthScoreData } from "@/lib/export/health-score-backup";
import { restoreVisitsData } from "@/lib/export/visits-backup";
import { restoreVaccinationsData } from "@/lib/export/vaccinations-backup";
import { invalidateUserData } from "@/lib/cache/invalidate";
export const dynamic = "force-dynamic";
@@ -92,6 +93,8 @@ interface RestoreResponse {
practitioners: number;
encounters: number;
encounterLinks: number;
vaccinations: number;
vaccinationLinks: number;
};
}
@@ -1332,6 +1335,18 @@ const handler = apiHandler(
skips,
);
// The immunization log, after the visits: a dose remaps its
// practitioner and its encounter against rows the branch above has
// just written, and its document link needs the documents that
// landed before that. Both ends of this section live in
// `src/lib/export/vaccinations-backup.ts`.
const vaccinationsCleared = await restoreVaccinationsData(
tx,
ownerId,
payload,
skips,
);
const cleared = {
measurements: measurements.count,
medications: meds.count,
@@ -1361,6 +1376,8 @@ const handler = apiHandler(
practitioners: visitsCleared.practitioners,
encounters: visitsCleared.encounters,
encounterLinks: visitsCleared.encounterLinks,
vaccinations: vaccinationsCleared.vaccinations,
vaccinationLinks: vaccinationsCleared.vaccinationLinks,
};
return { cleared, skipped: summarizeRestoreSkips(skips) };
},
@@ -36,6 +36,8 @@ vi.mock("@/lib/db", () => ({
encounterDocumentLink: { findMany: vi.fn().mockResolvedValue([]) },
encounterLabLink: { findMany: vi.fn().mockResolvedValue([]) },
encounterConditionLink: { findMany: vi.fn().mockResolvedValue([]) },
vaccinationRecord: { findMany: vi.fn().mockResolvedValue([]) },
vaccinationDocumentLink: { findMany: vi.fn().mockResolvedValue([]) },
// v1.15.0 — cycle tables read by the full-backup helper.
cycleProfile: { findUnique: vi.fn() },
menstrualCycle: { findMany: vi.fn() },
@@ -115,6 +117,10 @@ beforeEach(() => {
vi.mocked(prisma.encounterConditionLink.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.vaccinationRecord.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.vaccinationDocumentLink.findMany).mockResolvedValue(
[] as never,
);
});
afterEach(() => {
@@ -34,6 +34,8 @@ vi.mock("@/lib/db", () => ({
encounterDocumentLink: { findMany: vi.fn().mockResolvedValue([]) },
encounterLabLink: { findMany: vi.fn().mockResolvedValue([]) },
encounterConditionLink: { findMany: vi.fn().mockResolvedValue([]) },
vaccinationRecord: { findMany: vi.fn().mockResolvedValue([]) },
vaccinationDocumentLink: { findMany: vi.fn().mockResolvedValue([]) },
cycleProfile: { findUnique: vi.fn().mockResolvedValue(null) },
menstrualCycle: { findMany: vi.fn().mockResolvedValue([]) },
cycleDayLog: { findMany: vi.fn().mockResolvedValue([]) },