From 8d22669bef37da629ee48ba5eeb9bc1741f0668d Mon Sep 17 00:00:00 2001 From: Daniel Volz Date: Sun, 18 Jan 2026 09:19:23 +0100 Subject: [PATCH] fix: export/import dismissed doses and person-specific dose IDs (#45) - Add 'dismissed' field to dose history export/import - Add 'takenByPerson' field to handle person-suffixed dose IDs (e.g., 5-0-timestamp-Daniel) - Update parseDoseId() to extract person suffix from dose ID - Update buildDoseId() to include optional person suffix This fixes import losing: 1. Which past doses were marked as taken 2. Which doses were dismissed (cleared missed) 3. Person-specific dose tracking for shared schedules --- backend/src/routes/export.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/backend/src/routes/export.ts b/backend/src/routes/export.ts index ff0e228..f4ff8d2 100644 --- a/backend/src/routes/export.ts +++ b/backend/src/routes/export.ts @@ -55,6 +55,8 @@ const doseHistorySchema = z.object({ scheduledTime: z.string(), // ISO datetime takenAt: z.string(), // ISO datetime markedBy: z.string().nullable().optional(), + dismissed: z.boolean().default(false), + takenByPerson: z.string().nullable().optional(), // Person suffix from dose ID (e.g., "Daniel") }); const shareLinkSchema = z.object({ @@ -204,8 +206,8 @@ function base64ToImage(base64: string, medicationId: number): string | null { } // Parse dose ID to extract medication ID and timestamp -// Format: "{medicationId}-{blisterIndex}-{timestampMs}" -function parseDoseId(doseId: string): { medicationId: number; blisterIndex: number; timestampMs: number } | null { +// Format: "{medicationId}-{blisterIndex}-{timestampMs}" or "{medicationId}-{blisterIndex}-{timestampMs}-{person}" +function parseDoseId(doseId: string): { medicationId: number; blisterIndex: number; timestampMs: number; person: string | null } | null { const parts = doseId.split("-"); if (parts.length < 3) return null; @@ -215,12 +217,16 @@ function parseDoseId(doseId: string): { medicationId: number; blisterIndex: numb if (isNaN(medicationId) || isNaN(blisterIndex) || isNaN(timestampMs)) return null; - return { medicationId, blisterIndex, timestampMs }; + // Check if there's a person suffix (4th part onwards, could be multi-part name) + const person = parts.length > 3 ? parts.slice(3).join("-") : null; + + return { medicationId, blisterIndex, timestampMs, person }; } -// Build dose ID from parts -function buildDoseId(medicationId: number, blisterIndex: number, timestampMs: number): string { - return `${medicationId}-${blisterIndex}-${timestampMs}`; +// Build dose ID from parts (with optional person suffix) +function buildDoseId(medicationId: number, blisterIndex: number, timestampMs: number, person?: string | null): string { + const base = `${medicationId}-${blisterIndex}-${timestampMs}`; + return person ? `${base}-${person}` : base; } // ============================================================================= @@ -309,6 +315,8 @@ export async function exportRoutes(app: FastifyInstance) { scheduledTime: scheduledTimeIso, takenAt: takenAtIso, markedBy: dose.markedBy, + dismissed: dose.dismissed ?? false, + takenByPerson: parsed.person, }; }).filter((d): d is NonNullable => d !== null); @@ -484,13 +492,15 @@ export async function exportRoutes(app: FastifyInstance) { // Convert ISO timestamp back to milliseconds for dose ID const timestampMs = new Date(dose.scheduledTime).getTime(); - const doseId = buildDoseId(newMedId, dose.scheduleIndex, timestampMs); + // Rebuild dose ID with optional person suffix + const doseId = buildDoseId(newMedId, dose.scheduleIndex, timestampMs, dose.takenByPerson); await db.insert(doseTracking).values({ userId, doseId, takenAt: new Date(dose.takenAt), markedBy: dose.markedBy || null, + dismissed: dose.dismissed ?? false, }); }