fix: wait for table rows before extracting eligibility data; add batch claim PDF download
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
NODE_ENV="development"
|
||||
HOST=0.0.0.0
|
||||
PORT=5000
|
||||
FRONTEND_URLS=http://localhost:3000,http://192.168.1.236
|
||||
FRONTEND_URLS=http://localhost:3000,http://192.168.1.236,https://communitydentistsoflowell.mydentalofficemanagement.com
|
||||
SELENIUM_AGENT_BASE_URL=http://localhost:5002
|
||||
JWT_SECRET = 'dentalsecret'
|
||||
DB_HOST=localhost
|
||||
|
||||
@@ -7,6 +7,7 @@ import { forwardToSeleniumClaimAgent } from "../services/seleniumClaimClient";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import axios from "axios";
|
||||
import archiver from "archiver";
|
||||
import { seleniumQueue } from "../queue/queues";
|
||||
import { Prisma } from "@repo/db/generated/prisma";
|
||||
import { Decimal } from "decimal.js";
|
||||
@@ -688,6 +689,101 @@ router.post(
|
||||
}
|
||||
);
|
||||
|
||||
// GET /api/claims/batch-pdf
|
||||
// Query params: date=YYYY-MM-DD (required), staffIds=1,2 (required)
|
||||
// Returns a ZIP archive of all INSURANCE_CLAIM PdfFile records for patients
|
||||
// scheduled on that date in the given staff columns.
|
||||
router.get(
|
||||
"/batch-pdf",
|
||||
async (req: Request, res: Response): Promise<any> => {
|
||||
const date = String(req.query.date ?? "").trim();
|
||||
const staffIdsRaw = String(req.query.staffIds ?? "").trim();
|
||||
|
||||
if (!date) return res.status(400).json({ error: "Missing date query param" });
|
||||
if (!staffIdsRaw) return res.status(400).json({ error: "Missing staffIds query param" });
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
|
||||
const staffIdFilter = new Set(
|
||||
staffIdsRaw.split(",").map(Number).filter((n) => Number.isFinite(n) && n > 0)
|
||||
);
|
||||
|
||||
try {
|
||||
const allAppointments = await storage.getAppointmentsByDateForUser(date, req.user.id);
|
||||
const appointments = allAppointments.filter((a) => staffIdFilter.has(Number(a.staffId)));
|
||||
|
||||
type PdfEntry = { filename: string; data: Buffer };
|
||||
const pdfEntries: PdfEntry[] = [];
|
||||
|
||||
for (const apt of appointments) {
|
||||
const patientId = apt.patientId;
|
||||
if (!patientId) continue;
|
||||
|
||||
// Claim PDFs from Selenium are stored in PdfGroup (titleKey=INSURANCE_CLAIM) → PdfFile (binary pdfData)
|
||||
const group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
|
||||
if (!group?.id) continue;
|
||||
|
||||
const raw = await storage.getPdfFilesByGroupId(group.id);
|
||||
const files = (Array.isArray(raw) ? raw : (raw as any).data ?? []) as Array<{ filename: string; pdfData: unknown }>;
|
||||
|
||||
for (const f of files) {
|
||||
if (!f.pdfData) continue;
|
||||
// Prisma Bytes → always convert to Buffer to satisfy archiver
|
||||
const buf = Buffer.isBuffer(f.pdfData)
|
||||
? f.pdfData
|
||||
: Buffer.from(f.pdfData as any);
|
||||
if (!buf.length) continue;
|
||||
// Sanitize filename: strip path components, keep safe chars
|
||||
const safeName = (f.filename || "claim.pdf").replace(/[/\\]/g, "_").trim() || "claim.pdf";
|
||||
pdfEntries.push({ filename: safeName, data: buf });
|
||||
}
|
||||
}
|
||||
|
||||
if (pdfEntries.length === 0) {
|
||||
return res.status(404).json({ error: "No claim PDFs found for the selected columns and date" });
|
||||
}
|
||||
|
||||
const zipFilename = `claims_${date}.zip`;
|
||||
res.setHeader("Content-Type", "application/zip");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${zipFilename}"`);
|
||||
|
||||
const archive = archiver("zip", { zlib: { level: 6 } });
|
||||
|
||||
// Capture archiver errors before any data is written
|
||||
let archiveError: Error | null = null;
|
||||
archive.on("error", (err) => {
|
||||
archiveError = err;
|
||||
console.error("[batch-pdf] archiver error:", err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: `ZIP creation failed: ${err.message}` });
|
||||
}
|
||||
});
|
||||
|
||||
archive.pipe(res);
|
||||
|
||||
const seenNames = new Map<string, number>();
|
||||
for (const entry of pdfEntries) {
|
||||
if (archiveError) break;
|
||||
const count = seenNames.get(entry.filename) ?? 0;
|
||||
seenNames.set(entry.filename, count + 1);
|
||||
const archiveName =
|
||||
count === 0
|
||||
? entry.filename
|
||||
: `${path.basename(entry.filename, ".pdf")}_${count}.pdf`;
|
||||
archive.append(entry.data, { name: archiveName });
|
||||
}
|
||||
|
||||
if (!archiveError) {
|
||||
await archive.finalize();
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("[batch-pdf] error:", err);
|
||||
if (!res.headersSent) {
|
||||
return res.status(500).json({ error: err?.message ?? "Batch PDF download failed" });
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// GET /api/claims/by-appointment/:appointmentId
|
||||
// Returns the most recent active (non-cancelled/void) claim with service lines and files
|
||||
router.get(
|
||||
|
||||
Reference in New Issue
Block a user