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:
Gitead
2026-04-27 00:25:24 -04:00
parent a279a3e7c1
commit 3e899376c3
838 changed files with 28488 additions and 773 deletions

View File

@@ -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") {

View File

@@ -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 };
}

View File

@@ -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}`);