feat: save insurance PDFs (eligibility/claims/preauth) to Cloud Storage instead of Postgres blobs
New saves go into the patient's Cloud Storage folder under category subfolders (Eligibility/Claims/PreAuth/Claim Status/Attachments) via a new storage.savePdfToCloudStorage helper, instead of the legacy PdfGroup/PdfFile Postgres blob tables. Old records and their read paths are left untouched for backward compatibility; PDF list/content/delete endpoints and viewers now transparently handle both legacy numeric ids and new "cloud:<id>" ids. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,7 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string {
|
||||
return runClaimStatusProcessor({
|
||||
enrichedPayload: data.enrichedPayload,
|
||||
insuranceId: data.insuranceId!,
|
||||
userId: data.userId,
|
||||
});
|
||||
}
|
||||
if (jobType === "claim-submit" || jobType === "claim-pre-auth") {
|
||||
@@ -82,6 +83,7 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string {
|
||||
claimId: data.claimId,
|
||||
variant: jobType === "claim-pre-auth" ? "claim-pre-auth" : "claimsubmit",
|
||||
socketId: data.socketId,
|
||||
userId: data.userId,
|
||||
});
|
||||
}
|
||||
if (jobType === "ddma-eligibility-check") {
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface BcbsMaEligibilityProcessorInput {
|
||||
export interface BcbsMaEligibilityProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ async function processBcbsMaResult(
|
||||
seleniumResult: any
|
||||
): Promise<BcbsMaEligibilityProcessorResult> {
|
||||
const output: BcbsMaEligibilityProcessorResult = {};
|
||||
let createdPdfFileId: number | null = null;
|
||||
let createdPdfFileId: number | string | null = null;
|
||||
|
||||
try {
|
||||
// Prefer names extracted from the BCBS MA results page (Demographic Information section)
|
||||
@@ -133,16 +133,17 @@ async function processBcbsMaResult(
|
||||
}
|
||||
|
||||
if (pdfBuffer && pdfFilename) {
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
createdPdfFileId = `cloud:${cloudFile.id}`;
|
||||
output.pdfUploadStatus = "PDF saved to Eligibility folder";
|
||||
output.pdfFilename = pdfFilename;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export interface CCAClaimProcessorInput {
|
||||
export async function runCCAClaimProcessor(
|
||||
input: CCAClaimProcessorInput,
|
||||
jobId: string
|
||||
): Promise<{ status: string; claimNumber?: string | null; pdfFileId?: number | null }> {
|
||||
): Promise<{ status: string; claimNumber?: string | null; pdfFileId?: number | string | null }> {
|
||||
const { enrichedPayload, userId, claimId, socketId } = input;
|
||||
|
||||
log("cca-claim-processor", "starting Python agent session", { claimId });
|
||||
@@ -106,19 +106,22 @@ export async function runCCAClaimProcessor(
|
||||
seleniumResult?.pdfFilename || `cca_claim_${claimId ?? "unknown"}_${Date.now()}.pdf`;
|
||||
|
||||
// Save PDF to patient's Claims document group
|
||||
let pdfFileId: number | null = null;
|
||||
let pdfFileId: number | string | null = null;
|
||||
if (pdfBase64 && enrichedPayload?.claim?.patientId) {
|
||||
try {
|
||||
const patientId = Number(enrichedPayload.claim.patientId);
|
||||
const pdfBuffer = Buffer.from(pdfBase64, "base64");
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
|
||||
}
|
||||
const created = await storage.createPdfFile(group.id!, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
pdfFileId = Number((created as any).id);
|
||||
}
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName || `Patient ${patientId}`,
|
||||
"Claims",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
pdfFileId = `cloud:${cloudFile.id}`;
|
||||
log("cca-claim-processor", "PDF saved", { pdfFilename, pdfFileId, patientId });
|
||||
} catch (e: any) {
|
||||
log("cca-claim-processor", "failed to save PDF", { error: e?.message });
|
||||
|
||||
@@ -59,7 +59,7 @@ export interface CCAEligibilityProcessorInput {
|
||||
export interface CCAEligibilityProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ async function processCCAResult(
|
||||
seleniumResult: any
|
||||
): Promise<CCAEligibilityProcessorResult> {
|
||||
const output: CCAEligibilityProcessorResult = {};
|
||||
let createdPdfFileId: number | null = null;
|
||||
let createdPdfFileId: number | string | null = null;
|
||||
|
||||
try {
|
||||
// 1) Resolve patient name
|
||||
@@ -143,18 +143,17 @@ async function processCCAResult(
|
||||
|
||||
// 6) Save PDF to patient document group
|
||||
if (pdfBuffer && pdfFilename) {
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
|
||||
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
createdPdfFileId = `cloud:${cloudFile.id}`;
|
||||
output.pdfUploadStatus = "PDF saved to Eligibility folder";
|
||||
output.pdfFilename = pdfFilename;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export interface CCAPreAuthProcessorInput {
|
||||
export async function runCCAPreAuthProcessor(
|
||||
input: CCAPreAuthProcessorInput,
|
||||
jobId: string
|
||||
): Promise<{ status: string; authNumber?: string | null; pdfFileId?: number | null }> {
|
||||
): Promise<{ status: string; authNumber?: string | null; pdfFileId?: number | string | null }> {
|
||||
const { enrichedPayload, userId, claimId, socketId } = input;
|
||||
|
||||
log("cca-preauth-processor", "starting Python agent session", { claimId });
|
||||
@@ -110,19 +110,22 @@ export async function runCCAPreAuthProcessor(
|
||||
}
|
||||
|
||||
// Save PDF to patient's PreAuth document group
|
||||
let pdfFileId: number | null = null;
|
||||
let pdfFileId: number | string | null = null;
|
||||
if (pdfBase64 && enrichedPayload?.claim?.patientId) {
|
||||
try {
|
||||
const patientId = Number(enrichedPayload.claim.patientId);
|
||||
const pdfBuffer = Buffer.from(pdfBase64, "base64");
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(patientId, "PreAuth", "INSURANCE_CLAIM_PREAUTH");
|
||||
}
|
||||
const created = await storage.createPdfFile(group.id!, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
pdfFileId = Number((created as any).id);
|
||||
}
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName || `Patient ${patientId}`,
|
||||
"PreAuth",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
pdfFileId = `cloud:${cloudFile.id}`;
|
||||
log("cca-preauth-processor", "PDF saved", { pdfFilename, pdfFileId, patientId });
|
||||
} catch (e: any) {
|
||||
log("cca-preauth-processor", "failed to save PDF", { error: e?.message });
|
||||
|
||||
@@ -15,17 +15,18 @@ import {
|
||||
export interface ClaimStatusProcessorInput {
|
||||
enrichedPayload: any;
|
||||
insuranceId: string; // memberId used to look up the patient
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export interface ClaimStatusProcessorResult {
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
}
|
||||
|
||||
export async function runClaimStatusProcessor(
|
||||
input: ClaimStatusProcessorInput
|
||||
): Promise<ClaimStatusProcessorResult> {
|
||||
const { enrichedPayload, insuranceId } = input;
|
||||
const { enrichedPayload, insuranceId, userId } = input;
|
||||
|
||||
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
||||
const result = await callPythonSync("/claim-status-check", {
|
||||
@@ -61,22 +62,19 @@ export async function runClaimStatusProcessor(
|
||||
}
|
||||
|
||||
if (pdfBuffer && generatedPdfPath) {
|
||||
const groupTitleKey = "CLAIM_STATUS";
|
||||
const groupTitle = "Claim Status";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim();
|
||||
const basename = path.basename(generatedPdfPath);
|
||||
const created = await storage.createPdfFile(group.id, basename, pdfBuffer);
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName || `Patient ${patient.id}`,
|
||||
"Claim Status",
|
||||
basename,
|
||||
pdfBuffer
|
||||
);
|
||||
const createdPdfFileId: number | string | null = `cloud:${cloudFile.id}`;
|
||||
|
||||
let createdPdfFileId: number | null = null;
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
|
||||
outputResult.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
outputResult.pdfUploadStatus = `PDF saved to Claim Status`;
|
||||
outputResult.pdfFileId = createdPdfFileId;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface ClaimSubmitProcessorInput {
|
||||
variant?: "claimsubmit" | "claim-pre-auth";
|
||||
/** When set, the frontend socket listener will handle the PDF download — skip auto-save */
|
||||
socketId?: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export interface ClaimSubmitProcessorResult {
|
||||
@@ -28,7 +29,8 @@ export interface ClaimSubmitProcessorResult {
|
||||
async function savePdfFromSelenium(
|
||||
pdf_url: string,
|
||||
patientId: number,
|
||||
variant: "claimsubmit" | "claim-pre-auth"
|
||||
variant: "claimsubmit" | "claim-pre-auth",
|
||||
userId: number
|
||||
) {
|
||||
try {
|
||||
const filename = path.basename(new URL(pdf_url).pathname);
|
||||
@@ -37,15 +39,19 @@ async function savePdfFromSelenium(
|
||||
|
||||
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
|
||||
|
||||
const groupTitleKey = variant === "claim-pre-auth" ? "INSURANCE_CLAIM_PREAUTH" : "INSURANCE_CLAIM";
|
||||
const groupTitle = variant === "claim-pre-auth" ? "Preauth" : "Claims";
|
||||
const category = variant === "claim-pre-auth" ? "PreAuth" : "Claims";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, groupTitleKey);
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(patientId, groupTitle, groupTitleKey);
|
||||
}
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
|
||||
|
||||
await storage.createPdfFile(group.id!, filename, resp.data);
|
||||
await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName || `Patient ${patientId}`,
|
||||
category,
|
||||
filename,
|
||||
Buffer.from(resp.data)
|
||||
);
|
||||
console.log(`[claimSubmitProcessor] PDF saved for patient ${patientId}: ${filename}`);
|
||||
} catch (err: any) {
|
||||
// Non-fatal — claim was submitted; just log the PDF failure
|
||||
@@ -56,7 +62,7 @@ async function savePdfFromSelenium(
|
||||
export async function runClaimSubmitProcessor(
|
||||
input: ClaimSubmitProcessorInput
|
||||
): Promise<ClaimSubmitProcessorResult> {
|
||||
const { enrichedPayload, files, claimId } = input;
|
||||
const { enrichedPayload, files, claimId, userId } = input;
|
||||
const variant = input.variant ?? "claimsubmit";
|
||||
|
||||
// Build the same payload shape the Python /claimsubmit endpoint expects
|
||||
@@ -97,7 +103,8 @@ export async function runClaimSubmitProcessor(
|
||||
await savePdfFromSelenium(
|
||||
result.pdf_url,
|
||||
Number(enrichedPayload.patientId),
|
||||
variant
|
||||
variant,
|
||||
userId
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ export interface CmspEligibilityHistoryRemainingProcessorInput {
|
||||
export interface CmspEligibilityHistoryRemainingProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
memberDetailsPdfFileId?: number | null;
|
||||
memberDetailsPdfFileId?: number | string | null;
|
||||
memberDetailsPdfFilename?: string | null;
|
||||
historyPdfFileId?: number | null;
|
||||
historyPdfFileId?: number | string | null;
|
||||
historyPdfFilename?: string | null;
|
||||
accumulatorPdfFileId?: number | null;
|
||||
accumulatorPdfFileId?: number | string | null;
|
||||
accumulatorPdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -108,20 +108,20 @@ export async function runCmspEligibilityHistoryRemainingProcessor(
|
||||
await storage.updatePatient(patient.id, updates);
|
||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
|
||||
// Helper: save a single PDF to the patient's ELIGIBILITY_STATUS group
|
||||
const saveToGroup = async (pdfPath: string): Promise<number | null> => {
|
||||
// Helper: save a single PDF to the patient's Cloud Storage Eligibility folder
|
||||
const patientName = `${patient!.firstName ?? ""} ${patient!.lastName ?? ""}`.trim() || `Patient ${patient!.id}`;
|
||||
const saveToGroup = async (pdfPath: string): Promise<number | string | null> => {
|
||||
try {
|
||||
const buffer = await fs.readFile(pdfPath);
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
return Number(created.id);
|
||||
}
|
||||
return null;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient!.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
path.basename(pdfPath),
|
||||
buffer
|
||||
);
|
||||
return `cloud:${cloudFile.id}`;
|
||||
} catch (e: any) {
|
||||
console.error("[cmspProcessor] saveToGroup failed:", e.message);
|
||||
return null;
|
||||
|
||||
@@ -88,15 +88,22 @@ async function pollUntilDone(
|
||||
throw new Error(`DDMA claim polling exhausted all attempts for session ${sessionId}`);
|
||||
}
|
||||
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
|
||||
try {
|
||||
const filename = path.basename(new URL(pdf_url).pathname);
|
||||
const seleniumPort = process.env.SELENIUM_PORT || "5002";
|
||||
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
|
||||
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
|
||||
if (!group) group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
|
||||
await storage.createPdfFile(group.id!, filename, resp.data);
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : `Patient ${patientId}`;
|
||||
await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName,
|
||||
"Claims",
|
||||
filename,
|
||||
Buffer.from(resp.data)
|
||||
);
|
||||
log("ddma-claim-processor", "PDF saved", { patientId, filename });
|
||||
} catch (err: any) {
|
||||
log("ddma-claim-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
|
||||
@@ -160,7 +167,7 @@ export async function runDDMAClaimProcessor(
|
||||
if (pdf_url && !socketId) {
|
||||
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
|
||||
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
|
||||
}
|
||||
|
||||
emitToSocket(socketId, "selenium:ddma_claim_completed", {
|
||||
|
||||
@@ -66,7 +66,7 @@ export interface DdmaEligibilityProcessorInput {
|
||||
export interface DdmaEligibilityProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ async function processDdmaResult(
|
||||
seleniumResult: any
|
||||
): Promise<DdmaEligibilityProcessorResult> {
|
||||
const output: DdmaEligibilityProcessorResult = {};
|
||||
let createdPdfFileId: number | null = null;
|
||||
let createdPdfFileId: number | string | null = null;
|
||||
|
||||
try {
|
||||
// 1) Resolve patient name (prefer selenium extraction → form data)
|
||||
@@ -185,18 +185,17 @@ async function processDdmaResult(
|
||||
|
||||
// 6) Save PDF to patient document group
|
||||
if (pdfBuffer && pdfFilename) {
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
|
||||
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
createdPdfFileId = `cloud:${cloudFile.id}`;
|
||||
output.pdfUploadStatus = "PDF saved to Eligibility folder";
|
||||
output.pdfFilename = pdfFilename;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ export interface DeltaInsEligibilityProcessorInput {
|
||||
export interface DeltaInsEligibilityProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ async function processDeltaInsResult(
|
||||
seleniumResult: any
|
||||
): Promise<DeltaInsEligibilityProcessorResult> {
|
||||
const output: DeltaInsEligibilityProcessorResult = {};
|
||||
let createdPdfFileId: number | null = null;
|
||||
let createdPdfFileId: number | string | null = null;
|
||||
|
||||
try {
|
||||
// 1) Resolve patient name
|
||||
@@ -132,18 +132,17 @@ async function processDeltaInsResult(
|
||||
|
||||
// 6) Save PDF to patient document group
|
||||
if (pdfBuffer && pdfFilename) {
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
|
||||
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
createdPdfFileId = `cloud:${cloudFile.id}`;
|
||||
output.pdfUploadStatus = "PDF saved to Eligibility folder";
|
||||
output.pdfFilename = pdfFilename;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface DentaQuestEligibilityProcessorInput {
|
||||
export interface DentaQuestEligibilityProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ async function processDentaQuestResult(
|
||||
seleniumResult: any
|
||||
): Promise<DentaQuestEligibilityProcessorResult> {
|
||||
const output: DentaQuestEligibilityProcessorResult = {};
|
||||
let createdPdfFileId: number | null = null;
|
||||
let createdPdfFileId: number | string | null = null;
|
||||
|
||||
try {
|
||||
const rawName =
|
||||
@@ -130,18 +130,17 @@ async function processDentaQuestResult(
|
||||
}
|
||||
|
||||
if (pdfBuffer && pdfFilename) {
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
|
||||
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
createdPdfFileId = `cloud:${cloudFile.id}`;
|
||||
output.pdfUploadStatus = "PDF saved to Eligibility folder";
|
||||
output.pdfFilename = pdfFilename;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ export interface EligibilityHistoryProcessorInput {
|
||||
export interface EligibilityHistoryProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
memberDetailsPdfFileId?: number | null;
|
||||
memberDetailsPdfFileId?: number | string | null;
|
||||
memberDetailsPdfFilename?: string | null;
|
||||
historyPdfFileId?: number | null;
|
||||
historyPdfFileId?: number | string | null;
|
||||
historyPdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -106,20 +106,20 @@ export async function runEligibilityHistoryProcessor(
|
||||
await storage.updatePatient(patient.id, updates);
|
||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
|
||||
// Helper: save a PDF buffer to the patient's ELIGIBILITY_STATUS group
|
||||
const saveToGroup = async (pdfPath: string): Promise<number | null> => {
|
||||
// Helper: save a PDF buffer to the patient's Cloud Storage Eligibility folder
|
||||
const patientName = `${patient!.firstName ?? ""} ${patient!.lastName ?? ""}`.trim() || `Patient ${patient!.id}`;
|
||||
const saveToGroup = async (pdfPath: string): Promise<number | string | null> => {
|
||||
try {
|
||||
const buffer = await fs.readFile(pdfPath);
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
return Number(created.id);
|
||||
}
|
||||
return null;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient!.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
path.basename(pdfPath),
|
||||
buffer
|
||||
);
|
||||
return `cloud:${cloudFile.id}`;
|
||||
} catch (e: any) {
|
||||
console.error("[eligibilityHistoryProcessor] saveToGroup failed:", e.message);
|
||||
return null;
|
||||
@@ -127,7 +127,7 @@ export async function runEligibilityHistoryProcessor(
|
||||
};
|
||||
|
||||
// Save eligibility PDF
|
||||
let eligibilityPdfFileId: number | null = null;
|
||||
let eligibilityPdfFileId: number | string | null = null;
|
||||
if (seleniumResult.pdf_path?.endsWith(".pdf")) {
|
||||
eligibilityPdfFileId = await saveToGroup(seleniumResult.pdf_path);
|
||||
outputResult.pdfUploadStatus = eligibilityPdfFileId
|
||||
@@ -146,7 +146,7 @@ export async function runEligibilityHistoryProcessor(
|
||||
}
|
||||
|
||||
// Save history PDF
|
||||
let historyPdfFileId: number | null = null;
|
||||
let historyPdfFileId: number | string | null = null;
|
||||
if (seleniumResult.history_pdf_path?.endsWith(".pdf")) {
|
||||
historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path);
|
||||
outputResult.historyPdfFilename = path.basename(seleniumResult.history_pdf_path);
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface EligibilityProcessorInput {
|
||||
export interface EligibilityProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
}
|
||||
|
||||
export async function runEligibilityProcessor(
|
||||
@@ -122,27 +122,23 @@ export async function runEligibilityProcessor(
|
||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
|
||||
// 6) Save PDF
|
||||
let createdPdfFileId: number | null = null;
|
||||
let createdPdfFileId: number | string | null = null;
|
||||
|
||||
if (seleniumResult.pdf_path?.endsWith(".pdf")) {
|
||||
try {
|
||||
const pdfBuffer = await fs.readFile(seleniumResult.pdf_path);
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
|
||||
const created = await storage.createPdfFile(
|
||||
group.id,
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
path.basename(seleniumResult.pdf_path),
|
||||
pdfBuffer
|
||||
);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
outputResult.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
createdPdfFileId = `cloud:${cloudFile.id}`;
|
||||
outputResult.pdfUploadStatus = "PDF saved to Eligibility folder";
|
||||
} catch (e: any) {
|
||||
outputResult.pdfUploadStatus = `PDF upload failed: ${e.message}`;
|
||||
}
|
||||
|
||||
@@ -87,15 +87,22 @@ async function pollUntilDone(
|
||||
throw new Error(`Tufts SCO claim polling exhausted all attempts for session ${sessionId}`);
|
||||
}
|
||||
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
|
||||
try {
|
||||
const filename = path.basename(new URL(pdf_url).pathname);
|
||||
const seleniumPort = process.env.SELENIUM_PORT || "5002";
|
||||
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
|
||||
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
|
||||
if (!group) group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
|
||||
await storage.createPdfFile(group.id!, filename, resp.data);
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
|
||||
await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName || `Patient ${patientId}`,
|
||||
"Claims",
|
||||
filename,
|
||||
Buffer.from(resp.data)
|
||||
);
|
||||
log("tuftssco-claim-processor", "PDF saved", { patientId, filename });
|
||||
} catch (err: any) {
|
||||
log("tuftssco-claim-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
|
||||
@@ -151,7 +158,7 @@ export async function runTuftsSCOClaimProcessor(
|
||||
if (pdf_url && !socketId) {
|
||||
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
|
||||
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
|
||||
}
|
||||
|
||||
emitToSocket(socketId, "selenium:tuftssco_claim_completed", {
|
||||
|
||||
@@ -88,16 +88,21 @@ async function pollUntilDone(
|
||||
throw new Error(`Tufts SCO preauth polling exhausted all attempts for session ${sessionId}`);
|
||||
}
|
||||
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
|
||||
try {
|
||||
const filename = path.basename(new URL(pdf_url).pathname);
|
||||
const resp = await axios.get(pdf_url, { responseType: "arraybuffer", timeout: 30000 });
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(patientId, "PreAuth", "INSURANCE_CLAIM_PREAUTH");
|
||||
}
|
||||
await storage.createPdfFile(group.id!, filename, resp.data);
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
|
||||
await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName || `Patient ${patientId}`,
|
||||
"PreAuth",
|
||||
filename,
|
||||
Buffer.from(resp.data)
|
||||
);
|
||||
log("tuftssco-preauth-processor", "PDF saved", { patientId, filename });
|
||||
} catch (err: any) {
|
||||
log("tuftssco-preauth-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
|
||||
@@ -158,7 +163,7 @@ export async function runTuftsSCOPreAuthProcessor(
|
||||
if (pdf_url && !socketId) {
|
||||
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
|
||||
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
|
||||
}
|
||||
|
||||
emitToSocket(socketId, "selenium:tuftssco_preauth_completed", {
|
||||
|
||||
@@ -99,18 +99,23 @@ async function pollUntilDone(
|
||||
throw new Error(`UnitedDH claim polling exhausted all attempts for session ${sessionId}`);
|
||||
}
|
||||
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
|
||||
try {
|
||||
const filename = path.basename(new URL(pdf_url).pathname);
|
||||
const seleniumPort = process.env.SELENIUM_PORT || "5002";
|
||||
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
|
||||
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM");
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(patientId, "Claims", "INSURANCE_CLAIM");
|
||||
}
|
||||
await storage.createPdfFile(group.id!, filename, resp.data);
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
|
||||
await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName || `Patient ${patientId}`,
|
||||
"Claims",
|
||||
filename,
|
||||
Buffer.from(resp.data)
|
||||
);
|
||||
log("uniteddh-claim-processor", "PDF saved", { patientId, filename });
|
||||
} catch (err: any) {
|
||||
log("uniteddh-claim-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
|
||||
@@ -173,7 +178,7 @@ export async function runUnitedDHClaimProcessor(
|
||||
if (pdf_url && !socketId) {
|
||||
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
|
||||
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
|
||||
}
|
||||
|
||||
emitToSocket(socketId, "selenium:uniteddh_claim_completed", {
|
||||
|
||||
@@ -98,18 +98,23 @@ async function pollUntilDone(
|
||||
throw new Error(`UnitedDH preauth polling exhausted all attempts for session ${sessionId}`);
|
||||
}
|
||||
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
|
||||
async function savePdfFromSelenium(pdf_url: string, patientId: number, userId: number) {
|
||||
try {
|
||||
const filename = path.basename(new URL(pdf_url).pathname);
|
||||
const seleniumPort = process.env.SELENIUM_PORT || "5002";
|
||||
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
|
||||
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(patientId, "PreAuth", "INSURANCE_CLAIM_PREAUTH");
|
||||
}
|
||||
await storage.createPdfFile(group.id!, filename, resp.data);
|
||||
const patient = await storage.getPatient(patientId);
|
||||
const patientName = patient ? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() : "";
|
||||
await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patientId,
|
||||
patientName || `Patient ${patientId}`,
|
||||
"PreAuth",
|
||||
filename,
|
||||
Buffer.from(resp.data)
|
||||
);
|
||||
log("uniteddh-preauth-processor", "PDF saved", { patientId, filename });
|
||||
} catch (err: any) {
|
||||
log("uniteddh-preauth-processor", "failed to save PDF (non-fatal)", { error: err?.message ?? err });
|
||||
@@ -171,7 +176,7 @@ export async function runUnitedDHPreAuthProcessor(
|
||||
if (pdf_url && !socketId) {
|
||||
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
|
||||
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
|
||||
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId), userId);
|
||||
}
|
||||
|
||||
emitToSocket(socketId, "selenium:uniteddh_preauth_completed", {
|
||||
|
||||
@@ -58,7 +58,7 @@ export interface UnitedSCOEligibilityProcessorInput {
|
||||
export interface UnitedSCOEligibilityProcessorResult {
|
||||
patientUpdateStatus?: string;
|
||||
pdfUploadStatus?: string;
|
||||
pdfFileId?: number | null;
|
||||
pdfFileId?: number | string | null;
|
||||
pdfFilename?: string | null;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ async function processUnitedSCOResult(
|
||||
seleniumResult: any
|
||||
): Promise<UnitedSCOEligibilityProcessorResult> {
|
||||
const output: UnitedSCOEligibilityProcessorResult = {};
|
||||
let createdPdfFileId: number | null = null;
|
||||
let createdPdfFileId: number | string | null = null;
|
||||
|
||||
try {
|
||||
// 1) Resolve patient name
|
||||
@@ -155,18 +155,17 @@ async function processUnitedSCOResult(
|
||||
|
||||
// 6) Save PDF to patient document group
|
||||
if (pdfBuffer && pdfFilename) {
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const groupTitle = "Eligibility Status";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(patient.id, groupTitleKey);
|
||||
if (!group) group = await storage.createPdfGroup(patient.id, groupTitle, groupTitleKey);
|
||||
if (!group?.id) throw new Error("PDF group creation failed");
|
||||
|
||||
const created = await storage.createPdfFile(group.id, pdfFilename, pdfBuffer);
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
createdPdfFileId = Number(created.id);
|
||||
}
|
||||
output.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
const patientName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim() || `Patient ${patient.id}`;
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
userId,
|
||||
patient.id,
|
||||
patientName,
|
||||
"Eligibility",
|
||||
pdfFilename,
|
||||
pdfBuffer
|
||||
);
|
||||
createdPdfFileId = `cloud:${cloudFile.id}`;
|
||||
output.pdfUploadStatus = "PDF saved to Eligibility folder";
|
||||
output.pdfFilename = pdfFilename;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
|
||||
result = await runClaimStatusProcessor({
|
||||
enrichedPayload,
|
||||
insuranceId: job.data.insuranceId!,
|
||||
userId,
|
||||
});
|
||||
} else if (jobType === "claim-submit") {
|
||||
result = await runClaimSubmitProcessor({
|
||||
@@ -58,6 +59,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
|
||||
claimId: job.data.claimId,
|
||||
variant: "claimsubmit",
|
||||
socketId,
|
||||
userId,
|
||||
});
|
||||
} else if (jobType === "claim-pre-auth") {
|
||||
result = await runClaimSubmitProcessor({
|
||||
@@ -66,6 +68,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
|
||||
claimId: job.data.claimId,
|
||||
variant: "claim-pre-auth",
|
||||
socketId,
|
||||
userId,
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unknown selenium jobType: ${jobType}`);
|
||||
|
||||
@@ -113,6 +113,11 @@ router.post(
|
||||
|
||||
try {
|
||||
const folder = await storage.getOrCreatePatientFolder(req.user.id, patientId, patientName);
|
||||
const attachmentsFolder = await storage.getOrCreateSubfolder(
|
||||
req.user.id,
|
||||
(folder as any).id,
|
||||
"Attachments"
|
||||
);
|
||||
const result: { filename: string; mimeType: string; filePath: string }[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
@@ -126,14 +131,14 @@ router.post(
|
||||
file.buffer
|
||||
);
|
||||
|
||||
// Save to cloud storage patient folder (Cloud Storage page)
|
||||
// Save to cloud storage patient's Attachments subfolder (Cloud Storage page)
|
||||
const cloudFile = await storage.initializeFileUpload(
|
||||
req.user.id,
|
||||
file.originalname,
|
||||
file.mimetype,
|
||||
BigInt(file.size),
|
||||
1,
|
||||
(folder as any).id
|
||||
(attachmentsFolder as any).id
|
||||
);
|
||||
await storage.appendFileChunk((cloudFile as any).id, 0, file.buffer);
|
||||
const finalized = await storage.finalizeFileUpload((cloudFile as any).id);
|
||||
@@ -337,36 +342,28 @@ router.post(
|
||||
);
|
||||
}
|
||||
|
||||
const GROUP_TITLES: Record<GroupKey, string> = {
|
||||
const CLOUD_CATEGORIES: Record<GroupKey, "Claims" | "PreAuth"> = {
|
||||
INSURANCE_CLAIM: "Claims",
|
||||
INSURANCE_CLAIM_PREAUTH: "Preauth",
|
||||
INSURANCE_CLAIM_PREAUTH: "PreAuth",
|
||||
};
|
||||
const groupTitle = GROUP_TITLES[groupTitleKey];
|
||||
const cloudCategory = CLOUD_CATEGORIES[groupTitleKey];
|
||||
|
||||
// ✅ Find or create PDF group for this claim
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(
|
||||
const patient = await storage.getPatient(parsedPatientId);
|
||||
const patientName = patient
|
||||
? `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim()
|
||||
: "";
|
||||
|
||||
// ✅ Save PDF file into the patient's Cloud Storage category subfolder
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
req.user.id,
|
||||
parsedPatientId,
|
||||
groupTitleKey
|
||||
patientName || `Patient ${parsedPatientId}`,
|
||||
cloudCategory,
|
||||
filename,
|
||||
Buffer.from(pdfResponse.data)
|
||||
);
|
||||
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(
|
||||
parsedPatientId,
|
||||
groupTitle,
|
||||
groupTitleKey
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ Save PDF file into that group
|
||||
const created = await storage.createPdfFile(group.id!, filename, pdfResponse.data);
|
||||
|
||||
// Extract the PDF file ID for opening the viewer
|
||||
let pdfFileId: number | null = null;
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
pdfFileId = Number(created.id);
|
||||
} else if (typeof created === "number") {
|
||||
pdfFileId = created;
|
||||
}
|
||||
const pdfFileId: string = `cloud:${cloudFile.id}`;
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
@@ -942,6 +939,20 @@ router.get(
|
||||
const safeName = (f.filename || "claim.pdf").replace(/[/\\]/g, "_").trim() || "claim.pdf";
|
||||
pdfEntries.push({ filename: safeName, data: buf });
|
||||
}
|
||||
|
||||
// New claim PDFs are saved to the patient's Cloud Storage "Claims" subfolder
|
||||
const cloudFiles = await storage.listPatientCategoryCloudFiles(patientId, "Claims");
|
||||
for (const cf of cloudFiles) {
|
||||
if (cf.id == null) continue;
|
||||
const full = await storage.getFile(cf.id);
|
||||
if (!full?.diskPath) continue;
|
||||
const abs = path.join(process.cwd(), full.diskPath);
|
||||
if (!fs.existsSync(abs)) continue;
|
||||
const buf = fs.readFileSync(abs);
|
||||
if (!buf.length) continue;
|
||||
const safeName = (cf.name || "claim.pdf").replace(/[/\\]/g, "_").trim() || "claim.pdf";
|
||||
pdfEntries.push({ filename: safeName, data: buf });
|
||||
}
|
||||
}
|
||||
|
||||
if (pdfEntries.length === 0) {
|
||||
|
||||
@@ -150,6 +150,34 @@ router.post(
|
||||
}
|
||||
);
|
||||
|
||||
// Maps a legacy PdfGroup.titleKey to the matching Cloud Storage subfolder name
|
||||
// so new (cloud-backed) PDFs show up alongside old (DB-blob) PDFs for the same category.
|
||||
const TITLE_KEY_TO_CLOUD_CATEGORY: Record<string, string> = {
|
||||
ELIGIBILITY_STATUS: "Eligibility",
|
||||
INSURANCE_CLAIM: "Claims",
|
||||
INSURANCE_CLAIM_PREAUTH: "PreAuth",
|
||||
CLAIM_STATUS: "Claim Status",
|
||||
OTHER: "Attachments",
|
||||
};
|
||||
|
||||
async function listCloudPdfsForGroup(groupId: number) {
|
||||
const group = await storage.getPdfGroupById(groupId);
|
||||
if (!group) return [];
|
||||
const category = TITLE_KEY_TO_CLOUD_CATEGORY[group.titleKey as string];
|
||||
if (!category) return [];
|
||||
|
||||
const cloudFiles = await storage.listPatientCategoryCloudFiles(
|
||||
group.patientId,
|
||||
category
|
||||
);
|
||||
return cloudFiles.map((f) => ({
|
||||
id: `cloud:${f.id}`,
|
||||
filename: f.name,
|
||||
uploadedAt: f.createdAt,
|
||||
mimeType: f.mimeType,
|
||||
}));
|
||||
}
|
||||
|
||||
router.get(
|
||||
"/pdf-files/group/:groupId",
|
||||
async (req: Request, res: Response): Promise<any> => {
|
||||
@@ -160,7 +188,8 @@ router.get(
|
||||
}
|
||||
const groupId = parseInt(idParam);
|
||||
const files = await storage.getPdfFilesByGroupId(groupId);
|
||||
res.json(files);
|
||||
const cloudFiles = await listCloudPdfsForGroup(groupId);
|
||||
res.json([...cloudFiles, ...(files as any[])]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
@@ -203,6 +232,8 @@ router.get(
|
||||
// Decide whether client asked for paginated response
|
||||
const wantsPagination = typeof limit === "number";
|
||||
|
||||
const cloudFiles = await listCloudPdfsForGroup(groupId);
|
||||
|
||||
if (wantsPagination) {
|
||||
// storage.getPdfFilesByGroupId with pagination should return { total, data }
|
||||
const result = await storage.getPdfFilesByGroupId(groupId, {
|
||||
@@ -214,14 +245,17 @@ router.get(
|
||||
// result should be { total, data }, but handle unexpected shapes defensively
|
||||
if (Array.isArray(result)) {
|
||||
// fallback: storage returned full array; compute total
|
||||
return res.json({ total: result.length, data: result });
|
||||
const merged = [...cloudFiles, ...result];
|
||||
return res.json({ total: merged.length, data: merged });
|
||||
}
|
||||
|
||||
return res.json(result);
|
||||
const merged = [...cloudFiles, ...(result as any).data];
|
||||
return res.json({ total: (result as any).total + cloudFiles.length, data: merged });
|
||||
} else {
|
||||
// no limit requested -> return all files for the group
|
||||
const all = (await storage.getPdfFilesByGroupId(groupId)) as PdfFile[];
|
||||
return res.json({ total: all.length, data: all });
|
||||
const merged = [...cloudFiles, ...(all as any[])];
|
||||
return res.json({ total: merged.length, data: merged });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("GET /pdf-files/group/:groupId error:", err);
|
||||
@@ -238,6 +272,31 @@ router.get(
|
||||
if (!idParam) {
|
||||
return res.status(400).json({ error: "Missing ID" });
|
||||
}
|
||||
|
||||
// Cloud-Storage-backed PDF (new saves): id looks like "cloud:123"
|
||||
const idStr = String(idParam);
|
||||
if (idStr.startsWith("cloud:")) {
|
||||
const cloudId = parseInt(idStr.slice("cloud:".length), 10);
|
||||
if (Number.isNaN(cloudId)) {
|
||||
return res.status(400).json({ error: "Invalid ID" });
|
||||
}
|
||||
const cloudFile = await storage.getFile(cloudId);
|
||||
if (!cloudFile) return res.status(404).json({ error: "PDF not found" });
|
||||
|
||||
res.setHeader("Content-Type", cloudFile.mimeType || "application/pdf");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="${cloudFile.name}"; filename*=UTF-8''${encodeURIComponent(cloudFile.name)}`
|
||||
);
|
||||
try {
|
||||
await storage.streamFileTo(res, cloudId);
|
||||
} catch (streamErr) {
|
||||
console.error("Error streaming cloud PDF file:", streamErr);
|
||||
if (!res.headersSent) res.status(500).json({ error: "Failed to stream PDF" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const id = parseInt(idParam, 10);
|
||||
if (Number.isNaN(id)) {
|
||||
return res.status(400).json({ error: "Invalid ID" });
|
||||
@@ -409,7 +468,18 @@ router.delete(
|
||||
if (!idParam) {
|
||||
return res.status(400).json({ error: "Missing ID" });
|
||||
}
|
||||
const id = parseInt(idParam);
|
||||
|
||||
const idStr = String(idParam);
|
||||
if (idStr.startsWith("cloud:")) {
|
||||
const cloudId = parseInt(idStr.slice("cloud:".length), 10);
|
||||
if (Number.isNaN(cloudId)) {
|
||||
return res.status(400).json({ error: "Invalid ID" });
|
||||
}
|
||||
const success = await storage.deleteFile(cloudId);
|
||||
return res.json({ success });
|
||||
}
|
||||
|
||||
const id = parseInt(idStr);
|
||||
|
||||
const success = await storage.deletePdfFile(id);
|
||||
res.json({ success });
|
||||
|
||||
@@ -521,36 +521,18 @@ router.post(
|
||||
) {
|
||||
try {
|
||||
const pdfBuf = await fs.readFile(seleniumResult.pdf_path);
|
||||
const groupTitle = "Eligibility Status";
|
||||
const groupTitleKey = "ELIGIBILITY_STATUS";
|
||||
const patientName = `${updatedPatient.firstName ?? ""} ${updatedPatient.lastName ?? ""}`.trim();
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKey(
|
||||
const cloudFile = await storage.savePdfToCloudStorage(
|
||||
req.user.id,
|
||||
updatedPatient.id,
|
||||
groupTitleKey
|
||||
);
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(
|
||||
updatedPatient.id,
|
||||
groupTitle,
|
||||
groupTitleKey
|
||||
);
|
||||
}
|
||||
if (!group?.id)
|
||||
throw new Error("Failed to create/find pdf group");
|
||||
|
||||
const created = await storage.createPdfFile(
|
||||
group.id,
|
||||
patientName || `Patient ${updatedPatient.id}`,
|
||||
"Eligibility",
|
||||
path.basename(seleniumResult.pdf_path),
|
||||
pdfBuf
|
||||
);
|
||||
|
||||
if (created && typeof created === "object" && "id" in created) {
|
||||
resultItem.pdfFileId = Number(created.id);
|
||||
} else if (typeof created === "number") {
|
||||
resultItem.pdfFileId = created;
|
||||
} else if (created && (created as any).id) {
|
||||
resultItem.pdfFileId = (created as any).id;
|
||||
}
|
||||
resultItem.pdfFileId = `cloud:${cloudFile.id}`;
|
||||
|
||||
resultItem.processed = true;
|
||||
} catch (pdfErr: any) {
|
||||
|
||||
@@ -143,6 +143,24 @@ export interface IStorage {
|
||||
patientId: number,
|
||||
patientName: string
|
||||
): Promise<CloudFolder>;
|
||||
getOrCreateSubfolder(
|
||||
userId: number,
|
||||
parentId: number,
|
||||
name: string
|
||||
): Promise<CloudFolder>;
|
||||
savePdfToCloudStorage(
|
||||
userId: number,
|
||||
patientId: number,
|
||||
patientName: string,
|
||||
category: "Eligibility" | "Claims" | "PreAuth" | "Claim Status" | "Attachments",
|
||||
filename: string,
|
||||
buffer: Buffer,
|
||||
mimeType?: string
|
||||
): Promise<CloudFile>;
|
||||
listPatientCategoryCloudFiles(
|
||||
patientId: number,
|
||||
category: string
|
||||
): Promise<CloudFile[]>;
|
||||
}
|
||||
|
||||
/* ------------------------------- Implementation ------------------------------- */
|
||||
@@ -546,6 +564,82 @@ export const cloudStorageStorage: IStorage = {
|
||||
return created as unknown as CloudFolder;
|
||||
},
|
||||
|
||||
async getOrCreateSubfolder(userId: number, parentId: number, name: string) {
|
||||
const existing = await db.cloudFolder.findFirst({
|
||||
where: { parentId, name },
|
||||
});
|
||||
if (existing) return existing as unknown as CloudFolder;
|
||||
|
||||
try {
|
||||
return await cloudStorageStorage.createFolder(userId, name, parentId);
|
||||
} catch (err: any) {
|
||||
// unique constraint on [userId, parentId, name] - another request created it first
|
||||
if (err?.code === "P2002") {
|
||||
const race = await db.cloudFolder.findFirst({ where: { parentId, name } });
|
||||
if (race) return race as unknown as CloudFolder;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
async savePdfToCloudStorage(
|
||||
userId: number,
|
||||
patientId: number,
|
||||
patientName: string,
|
||||
category: "Eligibility" | "Claims" | "PreAuth" | "Claim Status" | "Attachments",
|
||||
filename: string,
|
||||
buffer: Buffer,
|
||||
mimeType: string = "application/pdf"
|
||||
) {
|
||||
const patientFolder = await cloudStorageStorage.getOrCreatePatientFolder(
|
||||
userId,
|
||||
patientId,
|
||||
patientName
|
||||
);
|
||||
const subfolder = await cloudStorageStorage.getOrCreateSubfolder(
|
||||
userId,
|
||||
patientFolder.id!,
|
||||
category
|
||||
);
|
||||
const cloudFile = await cloudStorageStorage.initializeFileUpload(
|
||||
userId,
|
||||
filename,
|
||||
mimeType,
|
||||
BigInt(buffer.length),
|
||||
1,
|
||||
subfolder.id!
|
||||
);
|
||||
await cloudStorageStorage.appendFileChunk(cloudFile.id!, 0, buffer);
|
||||
await cloudStorageStorage.finalizeFileUpload(cloudFile.id!);
|
||||
const finalFile = await cloudStorageStorage.getFile(cloudFile.id!);
|
||||
if (!finalFile) throw new Error("Failed to finalize PDF upload to cloud storage");
|
||||
return finalFile;
|
||||
},
|
||||
|
||||
async listPatientCategoryCloudFiles(patientId: number, category: string) {
|
||||
const patientFolder = await db.cloudFolder.findFirst({ where: { patientId } });
|
||||
if (!patientFolder) return [];
|
||||
const subfolder = await db.cloudFolder.findFirst({
|
||||
where: { parentId: patientFolder.id, name: category },
|
||||
});
|
||||
if (!subfolder) return [];
|
||||
const files = await db.cloudFile.findMany({
|
||||
where: { folderId: subfolder.id, isComplete: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
mimeType: true,
|
||||
fileSize: true,
|
||||
folderId: true,
|
||||
isComplete: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
},
|
||||
});
|
||||
return files.map(serializeFile) as unknown as CloudFile[];
|
||||
},
|
||||
|
||||
// --- STREAM ---
|
||||
async streamFileTo(resStream: NodeJS.WritableStream, fileId: number) {
|
||||
const file = await db.cloudFile.findUnique({
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Maximize2, Minimize2, Download, X } from "lucide-react";
|
||||
import { viewDocument } from "@/lib/api/documents";
|
||||
|
||||
type Props = {
|
||||
fileId: number | null;
|
||||
fileId: number | string | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
initialFileName?: string | null;
|
||||
@@ -60,8 +60,8 @@ export default function DocumentsFilePreviewModal({
|
||||
setLoading(false);
|
||||
return;
|
||||
} else if (isPatientDocument && fileId) {
|
||||
// For patient documents, use the viewDocument API to get the URL
|
||||
const documentUrl = viewDocument(fileId);
|
||||
// For patient documents, use the viewDocument API to get the URL (always a numeric id)
|
||||
const documentUrl = viewDocument(Number(fileId));
|
||||
res = await fetch(documentUrl);
|
||||
} else {
|
||||
// For PDF files, use the existing endpoint
|
||||
|
||||
@@ -92,7 +92,7 @@ interface BcbsMaEligibilityButtonProps {
|
||||
isFormIncomplete: boolean;
|
||||
autoTrigger?: boolean;
|
||||
onAutoTriggered?: () => void;
|
||||
onPdfReady: (pdfId: number, fallbackFilename: string | null) => void;
|
||||
onPdfReady: (pdfId: number | string, fallbackFilename: string | null) => void;
|
||||
}
|
||||
|
||||
export function BcbsMaEligibilityButton({
|
||||
@@ -204,7 +204,7 @@ export function BcbsMaEligibilityButton({
|
||||
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const pdfId = data.result?.pdfFileId;
|
||||
if (pdfId) {
|
||||
onPdfReady(Number(pdfId), data.result?.pdfFilename ?? `eligibility_bcbs_ma_${memberId}.pdf`);
|
||||
onPdfReady(pdfId, data.result?.pdfFilename ?? `eligibility_bcbs_ma_${memberId}.pdf`);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "BCBS MA eligibility job failed.";
|
||||
|
||||
@@ -19,7 +19,7 @@ interface CCAEligibilityButtonProps {
|
||||
isFormIncomplete: boolean;
|
||||
autoTrigger?: boolean;
|
||||
onAutoTriggered?: () => void;
|
||||
onPdfReady: (pdfId: number, fallbackFilename: string | null) => void;
|
||||
onPdfReady: (pdfId: number | string, fallbackFilename: string | null) => void;
|
||||
}
|
||||
|
||||
export function CCAEligibilityButton({
|
||||
@@ -143,7 +143,7 @@ export function CCAEligibilityButton({
|
||||
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const pdfId = data.result?.pdfFileId;
|
||||
if (pdfId) {
|
||||
onPdfReady(Number(pdfId), data.result?.pdfFilename ?? `eligibility_cca_${memberId}.pdf`);
|
||||
onPdfReady(pdfId, data.result?.pdfFilename ?? `eligibility_cca_${memberId}.pdf`);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "CCA eligibility job failed.";
|
||||
|
||||
@@ -90,7 +90,7 @@ interface DdmaEligibilityButtonProps {
|
||||
isFormIncomplete: boolean;
|
||||
autoTrigger?: boolean;
|
||||
onAutoTriggered?: () => void;
|
||||
onPdfReady: (pdfId: number, fallbackFilename: string | null) => void;
|
||||
onPdfReady: (pdfId: number | string, fallbackFilename: string | null) => void;
|
||||
}
|
||||
|
||||
export function DdmaEligibilityButton({
|
||||
@@ -248,7 +248,7 @@ export function DdmaEligibilityButton({
|
||||
if (pdfId) {
|
||||
const filename =
|
||||
data.result?.pdfFilename ?? `eligibility_ddma_${memberId}.pdf`;
|
||||
onPdfReady(Number(pdfId), filename);
|
||||
onPdfReady(pdfId, filename);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "DDMA eligibility job failed.";
|
||||
@@ -326,7 +326,7 @@ export function DdmaEligibilityButton({
|
||||
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const pdfId = data.result?.pdfFileId;
|
||||
if (pdfId) {
|
||||
onPdfReady(Number(pdfId), data.result?.pdfFilename ?? `eligibility_ddma_${memberId}.pdf`);
|
||||
onPdfReady(pdfId, data.result?.pdfFilename ?? `eligibility_ddma_${memberId}.pdf`);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "DDMA eligibility job failed.";
|
||||
|
||||
@@ -89,7 +89,7 @@ interface DeltaInsEligibilityButtonProps {
|
||||
isFormIncomplete: boolean;
|
||||
autoTrigger?: boolean;
|
||||
onAutoTriggered?: () => void;
|
||||
onPdfReady: (pdfId: number, fallbackFilename: string | null) => void;
|
||||
onPdfReady: (pdfId: number | string, fallbackFilename: string | null) => void;
|
||||
}
|
||||
|
||||
export function DeltaInsEligibilityButton({
|
||||
@@ -246,7 +246,7 @@ export function DeltaInsEligibilityButton({
|
||||
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const pdfId = data.result?.pdfFileId;
|
||||
if (pdfId) {
|
||||
onPdfReady(Number(pdfId), data.result?.pdfFilename ?? `eligibility_deltains_${memberId}.pdf`);
|
||||
onPdfReady(pdfId, data.result?.pdfFilename ?? `eligibility_deltains_${memberId}.pdf`);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "Delta Ins eligibility job failed.";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
|
||||
export interface PdfPanelConfig {
|
||||
pdfId?: number | null;
|
||||
pdfId?: number | string | null;
|
||||
fallbackFilename?: string | null;
|
||||
label: string;
|
||||
autoDownload?: boolean;
|
||||
@@ -34,7 +34,7 @@ function parseFilename(header: string | null): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function usePdfBlob(open: boolean, pdfId?: number | null, fallbackFilename?: string | null) {
|
||||
function usePdfBlob(open: boolean, pdfId?: number | string | null, fallbackFilename?: string | null) {
|
||||
const [blobUrl, setBlobUrl] = useState<string | null>(null);
|
||||
const [filename, setFilename] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Maximize2, Minimize2 } from "lucide-react";
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
pdfId?: number | null;
|
||||
pdfId?: number | string | null;
|
||||
fallbackFilename?: string | null;
|
||||
autoDownload?: boolean;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ interface TuftsSCOEligibilityButtonProps {
|
||||
isFormIncomplete: boolean;
|
||||
autoTrigger?: boolean;
|
||||
onAutoTriggered?: () => void;
|
||||
onPdfReady: (pdfId: number, fallbackFilename: string | null) => void;
|
||||
onPdfReady: (pdfId: number | string, fallbackFilename: string | null) => void;
|
||||
}
|
||||
|
||||
export function TuftsSCOEligibilityButton({
|
||||
@@ -243,7 +243,7 @@ export function TuftsSCOEligibilityButton({
|
||||
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const pdfId = data.result?.pdfFileId;
|
||||
if (pdfId) {
|
||||
onPdfReady(Number(pdfId), data.result?.pdfFilename ?? `eligibility_unitedsco_${memberId}.pdf`);
|
||||
onPdfReady(pdfId, data.result?.pdfFilename ?? `eligibility_unitedsco_${memberId}.pdf`);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "Tufts SCO eligibility job failed.";
|
||||
|
||||
@@ -89,7 +89,7 @@ interface UnitedSCOEligibilityButtonProps {
|
||||
isFormIncomplete: boolean;
|
||||
autoTrigger?: boolean;
|
||||
onAutoTriggered?: () => void;
|
||||
onPdfReady: (pdfId: number, fallbackFilename: string | null) => void;
|
||||
onPdfReady: (pdfId: number | string, fallbackFilename: string | null) => void;
|
||||
}
|
||||
|
||||
export function UnitedSCOEligibilityButton({
|
||||
@@ -243,7 +243,7 @@ export function UnitedSCOEligibilityButton({
|
||||
queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
const pdfId = data.result?.pdfFileId;
|
||||
if (pdfId) {
|
||||
onPdfReady(Number(pdfId), data.result?.pdfFilename ?? `eligibility_unitedsco_${memberId}.pdf`);
|
||||
onPdfReady(pdfId, data.result?.pdfFilename ?? `eligibility_unitedsco_${memberId}.pdf`);
|
||||
}
|
||||
} else if (data.status === "failed") {
|
||||
const msg = data.error ?? "United SCO eligibility job failed.";
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function ClaimsPage() {
|
||||
>(null);
|
||||
// PDF preview modal state
|
||||
const [previewOpen, setPreviewOpen] = useState(false);
|
||||
const [previewPdfId, setPreviewPdfId] = useState<number | null>(null);
|
||||
const [previewPdfId, setPreviewPdfId] = useState<number | string | null>(null);
|
||||
const [previewFallbackFilename, setPreviewFallbackFilename] = useState<string | null>(null);
|
||||
const dispatch = useAppDispatch();
|
||||
const { status, message, show } = useAppSelector(
|
||||
|
||||
@@ -15,7 +15,13 @@ import { Eye, Trash, Download, FolderOpen, FileText, HardDrive } from "lucide-re
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||
import { PatientTable } from "@/components/patients/patient-table";
|
||||
import { Patient, PdfFile } from "@repo/db/types";
|
||||
import { Patient, PdfFile as PrismaPdfFile } from "@repo/db/types";
|
||||
|
||||
// A PDF list item as returned by /api/documents/pdf-files/group/:groupId and
|
||||
// /api/documents/recent-pdf-files/group/:groupId — id is a plain number for
|
||||
// legacy (Postgres-blob) records, or a "cloud:<id>" string for records saved
|
||||
// to the new Cloud Storage backend.
|
||||
type PdfFile = Omit<PrismaPdfFile, "id"> & { id: number | string };
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
@@ -50,7 +56,7 @@ export default function DocumentsPage() {
|
||||
const [showPatientDocuments, setShowPatientDocuments] = useState(false);
|
||||
|
||||
// Document preview state
|
||||
const [previewDocumentId, setPreviewDocumentId] = useState<number | null>(null);
|
||||
const [previewDocumentId, setPreviewDocumentId] = useState<number | string | null>(null);
|
||||
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
|
||||
|
||||
// Delete document state
|
||||
@@ -245,7 +251,7 @@ export default function DocumentsPage() {
|
||||
|
||||
// DELETE mutation
|
||||
const deletePdfMutation = useMutation({
|
||||
mutationFn: async (id: number) => {
|
||||
mutationFn: async (id: number | string) => {
|
||||
await apiRequest("DELETE", `/api/documents/pdf-files/${id}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -267,7 +273,7 @@ export default function DocumentsPage() {
|
||||
|
||||
const handleConfirmDeletePdf = () => {
|
||||
if (currentPdf) {
|
||||
deletePdfMutation.mutate(Number(currentPdf.id));
|
||||
deletePdfMutation.mutate(currentPdf.id);
|
||||
} else {
|
||||
toast({
|
||||
title: "Error",
|
||||
@@ -277,12 +283,12 @@ export default function DocumentsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewPdf = (pdfId: number, filename?: string) => {
|
||||
const handleViewPdf = (pdfId: number | string, filename?: string) => {
|
||||
setPreviewDocumentId(pdfId);
|
||||
setIsPreviewModalOpen(true);
|
||||
};
|
||||
|
||||
const handleDownloadPdf = async (pdfId: number, filename: string) => {
|
||||
const handleDownloadPdf = async (pdfId: number | string, filename: string) => {
|
||||
const res = await apiRequest("GET", `/api/documents/pdf-files/${pdfId}`);
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
||||
@@ -447,7 +453,7 @@ export default function DocumentsPage() {
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleViewPdf(
|
||||
Number(pdf.id),
|
||||
pdf.id,
|
||||
pdf.filename
|
||||
)
|
||||
}
|
||||
@@ -459,7 +465,7 @@ export default function DocumentsPage() {
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleDownloadPdf(
|
||||
Number(pdf.id),
|
||||
pdf.id,
|
||||
pdf.filename
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,14 +142,14 @@ export default function InsuranceStatusPage() {
|
||||
|
||||
// PDF preview modal state
|
||||
const [previewOpen, setPreviewOpen] = useState(false);
|
||||
const [previewPdfId, setPreviewPdfId] = useState<number | null>(null);
|
||||
const [previewPdfId, setPreviewPdfId] = useState<number | string | null>(null);
|
||||
const [previewFallbackFilename, setPreviewFallbackFilename] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
// Dual PDF modal state (used by MH Eligibility & History)
|
||||
const [dualPreviewOpen, setDualPreviewOpen] = useState(false);
|
||||
const [dualEligibilityPdfId, setDualEligibilityPdfId] = useState<number | null>(null);
|
||||
const [dualEligibilityPdfId, setDualEligibilityPdfId] = useState<number | string | null>(null);
|
||||
const [dualEligibilityFilename, setDualEligibilityFilename] = useState<string | null>(null);
|
||||
const [dualMemberDetailsPdfId, setDualMemberDetailsPdfId] = useState<number | null>(null);
|
||||
const [dualMemberDetailsFilename, setDualMemberDetailsFilename] = useState<string | null>(null);
|
||||
@@ -158,7 +158,7 @@ export default function InsuranceStatusPage() {
|
||||
|
||||
// CMSP PDF modal state (used by CMSP Eligibility & History & Remaining)
|
||||
const [cmspPreviewOpen, setCmspPreviewOpen] = useState(false);
|
||||
const [cmspEligibilityPdfId, setCmspEligibilityPdfId] = useState<number | null>(null);
|
||||
const [cmspEligibilityPdfId, setCmspEligibilityPdfId] = useState<number | string | null>(null);
|
||||
const [cmspEligibilityFilename, setCmspEligibilityFilename] = useState<string | null>(null);
|
||||
const [cmspMemberDetailsPdfId, setCmspMemberDetailsPdfId] = useState<number | null>(null);
|
||||
const [cmspMemberDetailsFilename, setCmspMemberDetailsFilename] = useState<string | null>(null);
|
||||
@@ -381,7 +381,7 @@ export default function InsuranceStatusPage() {
|
||||
void tryAppointmentFromChatbot();
|
||||
|
||||
if (jobResult.pdfFileId) {
|
||||
setPreviewPdfId(Number(jobResult.pdfFileId));
|
||||
setPreviewPdfId(jobResult.pdfFileId);
|
||||
setPreviewFallbackFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
|
||||
setPreviewOpen(true);
|
||||
}
|
||||
@@ -466,7 +466,7 @@ export default function InsuranceStatusPage() {
|
||||
setSelectedPatient(null);
|
||||
|
||||
if (jobResult.pdfFileId) {
|
||||
setPreviewPdfId(Number(jobResult.pdfFileId));
|
||||
setPreviewPdfId(jobResult.pdfFileId);
|
||||
setPreviewFallbackFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
|
||||
setPreviewOpen(true);
|
||||
}
|
||||
@@ -579,7 +579,7 @@ export default function InsuranceStatusPage() {
|
||||
|
||||
// Open all PDFs side by side in the modal
|
||||
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId) {
|
||||
setDualEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null);
|
||||
setDualEligibilityPdfId(jobResult.pdfFileId ?? null);
|
||||
setDualEligibilityFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`);
|
||||
setDualMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null);
|
||||
setDualMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `eligibility_member_details_${memberId}.pdf`);
|
||||
@@ -649,7 +649,7 @@ export default function InsuranceStatusPage() {
|
||||
|
||||
// Open 4-panel modal
|
||||
if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) {
|
||||
setCmspEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null);
|
||||
setCmspEligibilityPdfId(jobResult.pdfFileId ?? null);
|
||||
setCmspEligibilityFilename(jobResult.pdfFilename ?? `cmsp_eligibility_${memberId}.pdf`);
|
||||
setCmspMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null);
|
||||
setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `cmsp_member_details_${memberId}.pdf`);
|
||||
|
||||
Reference in New Issue
Block a user