From 79bae13f3501fe9b88c7124f687c06d06b12b025 Mon Sep 17 00:00:00 2001 From: Gitead Date: Sat, 4 Jul 2026 14:17:40 -0400 Subject: [PATCH] fix: dedupe claim attachments to prevent duplicate uploads to insurance Claim attachments were being appended without checking for existing duplicates, causing files (e.g. the same PDF) to be sent to the insurance clearinghouse multiple times. --- apps/Backend/src/routes/claims.ts | 45 ++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/apps/Backend/src/routes/claims.ts b/apps/Backend/src/routes/claims.ts index 4c56d55f..24706c9f 100755 --- a/apps/Backend/src/routes/claims.ts +++ b/apps/Backend/src/routes/claims.ts @@ -740,13 +740,16 @@ router.post( if (saved) resolvedNpiProvider = saved; } - // Collect attachments: appointment-level files + claim-level files + // Collect attachments: appointment-level files + claim-level files (deduped by filename+path) const apptFiles = await storage.getAppointmentFiles(Number(apt.id)); const claimFiles = (activeClaim as any)?.claimFiles ?? []; - const allFileMeta = [ - ...apptFiles, - ...claimFiles, - ] as Array<{ filename: string; mimeType?: string | null; filePath?: string | null }>; + const seenFileKeys = new Set(); + const allFileMeta = [...apptFiles, ...claimFiles].filter((f: any) => { + const key = `${f.filename ?? ""}::${f.filePath ?? ""}`; + if (seenFileKeys.has(key)) return false; + seenFileKeys.add(key); + return true; + }) as Array<{ filename: string; mimeType?: string | null; filePath?: string | null }>; const filesForQueue = allFileMeta.flatMap((f) => { if (!f.filePath) return []; @@ -1276,17 +1279,33 @@ router.put("/:id", async (req: Request, res: Response): Promise => { await syncAppointmentType(existingClaim.appointmentId, updatedCodes); } - // If new claimFiles are provided, append them (don't delete existing ones) + // If new claimFiles are provided, append only ones not already attached to this claim if (Array.isArray(req.body.claimFiles) && req.body.claimFiles.length > 0) { const { prisma: db } = await import("@repo/db/client"); - await db.claimFile.createMany({ - data: req.body.claimFiles.map((f: any) => ({ - claimId, - filename: String(f.filename || ""), - mimeType: String(f.mimeType || f.mime || ""), - ...(f.filePath ? { filePath: String(f.filePath) } : {}), - })), + const existingFiles = await db.claimFile.findMany({ + where: { claimId }, + select: { filename: true, filePath: true }, }); + const existingKeys = new Set( + existingFiles.map((f: any) => `${f.filename}::${f.filePath ?? ""}`) + ); + const seenKeys = new Set(); + const newFiles = req.body.claimFiles.filter((f: any) => { + const key = `${String(f.filename || "")}::${String(f.filePath || "")}`; + if (existingKeys.has(key) || seenKeys.has(key)) return false; + seenKeys.add(key); + return true; + }); + if (newFiles.length > 0) { + await db.claimFile.createMany({ + data: newFiles.map((f: any) => ({ + claimId, + filename: String(f.filename || ""), + mimeType: String(f.mimeType || f.mime || ""), + ...(f.filePath ? { filePath: String(f.filePath) } : {}), + })), + }); + } } // Explicitly pick only scalar fields — skip serviceLines and claimFiles (handled above)