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>
This commit is contained in:
@@ -71,6 +71,7 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string {
|
||||
files: data.files ?? [],
|
||||
claimId: data.claimId,
|
||||
variant: jobType === "claim-pre-auth" ? "claim-pre-auth" : "claimsubmit",
|
||||
socketId: data.socketId,
|
||||
});
|
||||
}
|
||||
if (jobType === "ddma-eligibility-check") {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* 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";
|
||||
|
||||
@@ -11,6 +13,8 @@ export interface ClaimSubmitProcessorInput {
|
||||
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 {
|
||||
@@ -20,10 +24,40 @@ export interface ClaimSubmitProcessorResult {
|
||||
[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
|
||||
@@ -36,13 +70,12 @@ export async function runClaimSubmitProcessor(
|
||||
|
||||
const payload = { claim: enrichedPayload, pdfs, images };
|
||||
|
||||
const endpoint =
|
||||
input.variant === "claim-pre-auth" ? "/claim-pre-auth" : "/claimsubmit";
|
||||
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 after successful submission
|
||||
// 2) Persist claimNumber and update status to REVIEW
|
||||
if (claimId) {
|
||||
try {
|
||||
const updates: Record<string, any> = { status: "REVIEW" };
|
||||
@@ -53,5 +86,14 @@ export async function runClaimSubmitProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
// 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 };
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
|
||||
files: job.data.files ?? [],
|
||||
claimId: job.data.claimId,
|
||||
variant: "claimsubmit",
|
||||
socketId,
|
||||
});
|
||||
} else if (jobType === "claim-pre-auth") {
|
||||
result = await runClaimSubmitProcessor({
|
||||
@@ -64,6 +65,7 @@ async function processSeleniumJob(job: Job<SeleniumJobData>) {
|
||||
files: job.data.files ?? [],
|
||||
claimId: job.data.claimId,
|
||||
variant: "claim-pre-auth",
|
||||
socketId,
|
||||
});
|
||||
} else {
|
||||
throw new Error(`Unknown selenium jobType: ${jobType}`);
|
||||
|
||||
Reference in New Issue
Block a user