Files
DentalManagementMH04/apps/Backend/src/queue/processors/claimSubmitProcessor.ts
Gitead 3e899376c3 feat: Select Procedures flow, batch-column NPI provider fix, auto PDF save
- Add 'Select Procedures' right-click option on appointment page (separate from Claims/PreAuth)
- Select Procedures form saves CDT codes + NPI provider to AppointmentProcedure storage
- Remove Save button from insurance claim form; Claims/PreAuth opens for insurance submission only
- Claims/PreAuth auto-prefills from saved procedures including NPI provider
- Batch-column: procedures npiProviderId takes priority over stale claim npiProviderId
- Batch-column: auto-save PDF to patient Documents after successful submission (no socket needed)
- Add npiProviderId column to AppointmentProcedure table (prisma db push)
- Fix 'invalid db creation invocation': guard staffId, npiProviderId, procedureDate as Date object, totalBilled NaN guard
- Add full error logging to batch-column catch block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 00:25:24 -04:00

100 lines
3.6 KiB
TypeScript

/**
* Processors for "claim-submit" and "claim-pre-auth" jobs.
* Mirrors routes/claims.ts /selenium-claim and /selenium-claim-pre-auth
*/
import path from "path";
import axios from "axios";
import { storage } from "../../storage";
import { callPythonSync } from "./_shared";
export interface ClaimSubmitProcessorInput {
enrichedPayload: any;
files: { originalname: string; bufferBase64: string; mimetype: string }[];
claimId?: number;
/** "claimsubmit" (default) or "claim-pre-auth" */
variant?: "claimsubmit" | "claim-pre-auth";
/** When set, the frontend socket listener will handle the PDF download — skip auto-save */
socketId?: string;
}
export interface ClaimSubmitProcessorResult {
status: string;
claimNumber?: string;
pdf_url?: string;
[key: string]: any;
}
/** Fetch the PDF from the selenium service and save it to cloud storage. */
async function savePdfFromSelenium(
pdf_url: string,
patientId: number,
variant: "claimsubmit" | "claim-pre-auth"
) {
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 });
const groupTitleKey = variant === "claim-pre-auth" ? "INSURANCE_CLAIM_PREAUTH" : "INSURANCE_CLAIM";
const groupTitle = variant === "claim-pre-auth" ? "Claims Preauth" : "Claims";
let group = await storage.findPdfGroupByPatientTitleKey(patientId, groupTitleKey);
if (!group) {
group = await storage.createPdfGroup(patientId, groupTitle, groupTitleKey);
}
await storage.createPdfFile(group.id!, filename, resp.data);
console.log(`[claimSubmitProcessor] PDF saved for patient ${patientId}: ${filename}`);
} catch (err: any) {
// Non-fatal — claim was submitted; just log the PDF failure
console.error("[claimSubmitProcessor] failed to save PDF:", err?.message ?? err);
}
}
export async function runClaimSubmitProcessor(
input: ClaimSubmitProcessorInput
): Promise<ClaimSubmitProcessorResult> {
const { enrichedPayload, files, claimId } = input;
const variant = input.variant ?? "claimsubmit";
// Build the same payload shape the Python /claimsubmit endpoint expects
const pdfs = files
.filter((f) => f.mimetype === "application/pdf")
.map(({ originalname, bufferBase64 }) => ({ originalname, bufferBase64 }));
const images = files
.filter((f) => f.mimetype.startsWith("image/"))
.map(({ originalname, bufferBase64 }) => ({ originalname, bufferBase64 }));
const payload = { claim: enrichedPayload, pdfs, images };
const endpoint = variant === "claim-pre-auth" ? "/claim-pre-auth" : "/claimsubmit";
// 1) Call the Python service synchronously (BullMQ worker handles async)
const result = await callPythonSync(endpoint, payload, 10 * 60 * 1000);
// 2) Persist claimNumber and update status to REVIEW
if (claimId) {
try {
const updates: Record<string, any> = { status: "REVIEW" };
if (result?.claimNumber) updates.claimNumber = result.claimNumber;
await storage.updateClaim(claimId, updates);
} catch (e) {
console.error("[claimSubmitProcessor] failed to update claim after submission:", e);
}
}
// 3) Auto-save PDF for batch jobs (no socketId = no frontend listener to call fetchpdf)
if (result?.pdf_url && enrichedPayload?.patientId && !input.socketId) {
await savePdfFromSelenium(
result.pdf_url,
Number(enrichedPayload.patientId),
variant
);
}
return { ...result, claimId };
}