feat: save claim attachments to cloud storage and documents page

- Claim file uploads (chatbot or manual) now save to both the Cloud
  Storage patient folder and the Documents page via new
  POST /api/claims/upload-to-cloud endpoint
- MH submit flow now calls uploadAttachmentsToLocalFolder (same as
  DDMA/United/Tufts) so chatbot-attached X-rays are persisted
- Removed old /upload-attachments disk route and attachmentDiskStorage
  multer config; deleted uploads/patients/ folder
- uploadAttachmentsToLocalFolder now points to /upload-to-cloud and
  sends patientId so the backend can create the patient folder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-11 00:30:32 -04:00
parent 46daeb1c1f
commit d4b9c1b889
10 changed files with 280 additions and 68 deletions

View File

@@ -313,9 +313,9 @@ router.post("/create-appointment-today", async (req: Request, res: Response): Pr
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const { patientId } = req.body;
const { patientId, date } = req.body;
if (!patientId) return res.status(400).json({ message: "patientId is required" });
const result = await createAppointmentToday(Number(patientId), userId, storage);
const result = await createAppointmentToday(Number(patientId), userId, storage, date ?? undefined);
if ("error" in result) return res.status(409).json({ message: result.error });
return res.status(200).json(result);
} catch (err) {

View File

@@ -90,45 +90,66 @@ const upload = multer({
},
});
// Disk-storage uploader for claim attachments saved under uploads/patients/<name>/
const attachmentDiskStorage = multer.diskStorage({
destination: (req, _file, cb) => {
const patientName = String(req.body.patientName || "unknown").replace(/[/\\?%*:|"<>]/g, "-").trim();
const dir = path.join(process.cwd(), "uploads", "patients", patientName);
fs.mkdirSync(dir, { recursive: true });
cb(null, dir);
},
filename: (_req, file, cb) => {
const ext = path.extname(file.originalname);
const base = path.basename(file.originalname, ext).replace(/\s+/g, "_");
cb(null, `${Date.now()}_${base}${ext}`);
},
});
const attachmentUpload = multer({
storage: attachmentDiskStorage,
limits: { fileSize: 20 * 1024 * 1024 },
fileFilter: (_req, file, cb) => {
const allowed = ["application/pdf", "image/jpeg", "image/png", "image/webp"];
cb(null, allowed.includes(file.mimetype));
},
});
// POST /api/claims/upload-attachments
// Saves files to uploads/patients/<patientName>/ and returns their paths.
// POST /api/claims/upload-to-cloud
// Saves uploaded files to both:
// - Patient Documents (visible on Documents page)
// - Cloud Storage patient folder (visible on Cloud Storage page)
router.post(
"/upload-attachments",
attachmentUpload.array("files", 10),
(req: Request, res: Response): any => {
"/upload-to-cloud",
upload.array("files", 10),
async (req: Request, res: Response): Promise<any> => {
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
const files = req.files as Express.Multer.File[];
if (!files?.length) return res.status(400).json({ error: true, message: "No files uploaded" });
if (!files?.length) return res.status(400).json({ error: "No files uploaded" });
const result = files.map((f) => ({
filename: f.originalname,
mimeType: f.mimetype,
filePath: `/uploads/patients/${path.basename(path.dirname(f.path))}/${path.basename(f.path)}`,
}));
const patientId = Number(req.body.patientId);
const patientName = String(req.body.patientName || "unknown").replace(/[/\\?%*:|"<>]/g, "-").trim();
return res.json({ error: false, data: result });
if (!patientId || isNaN(patientId)) {
return res.status(400).json({ error: "Invalid patientId" });
}
try {
const folder = await storage.getOrCreatePatientFolder(req.user.id, patientId, patientName);
const result: { filename: string; mimeType: string; filePath: string }[] = [];
for (const file of files) {
// Save to patient-documents (Documents page)
await storage.createPatientDocument(
patientId,
file.originalname,
file.originalname,
file.mimetype,
file.size,
file.buffer
);
// Save to cloud storage patient folder (Cloud Storage page)
const cloudFile = await storage.initializeFileUpload(
req.user.id,
file.originalname,
file.mimetype,
BigInt(file.size),
1,
(folder as any).id
);
await storage.appendFileChunk((cloudFile as any).id, 0, file.buffer);
await storage.finalizeFileUpload((cloudFile as any).id);
result.push({
filename: file.originalname,
mimeType: file.mimetype,
filePath: `/api/cloud-storage/files/${(cloudFile as any).id}/content`,
});
}
return res.json({ error: false, data: result });
} catch (err: any) {
console.error("[upload-to-cloud]", err);
return res.status(500).json({ error: "Failed to upload files", message: err?.message });
}
}
);

View File

@@ -370,7 +370,7 @@ router.post("/backup-path", async (req, res) => {
});
}
const filename = `dental_backup_${Date.now()}.sql`;
const filename = `dental_backup_${Date.now()}.zip`;
try {
await backupDatabaseToPath({