fix: fix remote browser socket connection and related updates
This commit is contained in:
@@ -84,7 +84,7 @@ router.put("/set-npi-provider/:appointmentId", async (req: Request, res: Respons
|
||||
*/
|
||||
router.post("/save-for-appointment", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { appointmentId, patientId, npiProviderId, procedures } = req.body;
|
||||
const { appointmentId, patientId, npiProviderId, procedures, attachments } = req.body;
|
||||
|
||||
if (!appointmentId || isNaN(Number(appointmentId))) {
|
||||
return res.status(400).json({ message: "Invalid appointmentId" });
|
||||
@@ -100,6 +100,14 @@ router.post("/save-for-appointment", async (req: Request, res: Response) => {
|
||||
(p) => String(p.procedureCode ?? "").trim() !== ""
|
||||
);
|
||||
|
||||
const validAttachments = Array.isArray(attachments)
|
||||
? (attachments as any[]).filter((a) => a?.filename).map((a) => ({
|
||||
filename: String(a.filename),
|
||||
mimeType: a.mimeType ?? null,
|
||||
filePath: a.filePath ?? null,
|
||||
}))
|
||||
: undefined;
|
||||
|
||||
const count = await storage.saveForAppointment({
|
||||
appointmentId: Number(appointmentId),
|
||||
patientId: Number(patientId),
|
||||
@@ -110,6 +118,7 @@ router.post("/save-for-appointment", async (req: Request, res: Response) => {
|
||||
toothNumber: p.toothNumber || null,
|
||||
toothSurface: p.toothSurface || null,
|
||||
})),
|
||||
attachments: validAttachments,
|
||||
});
|
||||
|
||||
return res.json({ success: true, count });
|
||||
|
||||
@@ -425,18 +425,25 @@ router.post(
|
||||
// Fetch active claim for this appointment (includes service lines from draft saves)
|
||||
const activeClaim = await storage.getActiveClaimByAppointmentId(Number(apt.id));
|
||||
|
||||
// "Already claimed" = has a real claim number OR status is REVIEW/APPROVED
|
||||
// A PENDING claim with no claimNumber is just a draft save — not yet submitted
|
||||
const alreadyClaimed =
|
||||
// Skip if claim was voided via the "Void" button in Select Procedures.
|
||||
if (activeClaim?.status === "VOID") {
|
||||
resultItem.skipped = true;
|
||||
resultItem.error = "Voided";
|
||||
results.push(resultItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip appointments whose claim was already submitted (has claimNumber or REVIEW/APPROVED).
|
||||
// The "Update & Resubmit" button resets the claim to PENDING so it is picked up again.
|
||||
const alreadySubmitted =
|
||||
activeClaim &&
|
||||
((activeClaim.claimNumber != null &&
|
||||
String(activeClaim.claimNumber).trim() !== "") ||
|
||||
((activeClaim.claimNumber != null && String(activeClaim.claimNumber).trim() !== "") ||
|
||||
activeClaim.status === "REVIEW" ||
|
||||
activeClaim.status === "APPROVED");
|
||||
|
||||
if (alreadyClaimed) {
|
||||
if (alreadySubmitted) {
|
||||
resultItem.skipped = true;
|
||||
resultItem.error = "Already claimed";
|
||||
resultItem.error = "Already submitted";
|
||||
results.push(resultItem);
|
||||
continue;
|
||||
}
|
||||
@@ -544,7 +551,6 @@ router.post(
|
||||
let claimId: number;
|
||||
if (activeClaim?.id) {
|
||||
claimId = activeClaim.id;
|
||||
// Update claim's npiProviderId if the user chose a different provider via Select Procedures
|
||||
if (procNpiProviderId && activeClaim.npiProviderId !== procNpiProviderId) {
|
||||
await storage.updateClaim(claimId, { npiProviderId: procNpiProviderId });
|
||||
}
|
||||
@@ -634,12 +640,31 @@ router.post(
|
||||
};
|
||||
}
|
||||
|
||||
// Collect attachments: appointment-level files + claim-level files
|
||||
const apptFiles = await storage.getAppointmentFiles(Number(apt.id));
|
||||
const claimFiles = (activeClaim as any)?.claimFiles ?? [];
|
||||
const allFileMeta = [
|
||||
...apptFiles,
|
||||
...claimFiles,
|
||||
] as Array<{ filename: string; mimeType?: string | null; filePath?: string | null }>;
|
||||
|
||||
const filesForQueue = allFileMeta.flatMap((f) => {
|
||||
if (!f.filePath) return [];
|
||||
const absPath = path.join(process.cwd(), f.filePath);
|
||||
if (!fs.existsSync(absPath)) {
|
||||
console.warn(`[batch-column] attachment not found on disk: ${absPath}`);
|
||||
return [];
|
||||
}
|
||||
const bufferBase64 = fs.readFileSync(absPath).toString("base64");
|
||||
return [{ originalname: f.filename, bufferBase64, mimetype: f.mimeType ?? "application/octet-stream" }];
|
||||
});
|
||||
|
||||
// Enqueue selenium claim-submit job
|
||||
const job = await seleniumQueue.add("claim-submit", {
|
||||
jobType: "claim-submit",
|
||||
userId: req.user.id,
|
||||
enrichedPayload,
|
||||
files: [],
|
||||
files: filesForQueue,
|
||||
claimId,
|
||||
});
|
||||
|
||||
@@ -991,4 +1016,72 @@ router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/claims/void-for-appointment
|
||||
// Marks the claim for an appointment as VOID so batch-column skips it permanently.
|
||||
// If no claim exists yet, creates a minimal placeholder VOID claim.
|
||||
router.post("/void-for-appointment", async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
|
||||
const { appointmentId } = req.body;
|
||||
if (!appointmentId || isNaN(Number(appointmentId))) {
|
||||
return res.status(400).json({ error: "Invalid appointmentId" });
|
||||
}
|
||||
|
||||
const existing = await storage.getActiveClaimByAppointmentId(Number(appointmentId));
|
||||
if (existing) {
|
||||
await storage.updateClaim(Number(existing.id), { status: "VOID" } as any);
|
||||
return res.json({ voided: true, claimId: existing.id });
|
||||
}
|
||||
|
||||
// No claim yet — look up appointment + patient to create a minimal VOID placeholder
|
||||
const apt = await storage.getAppointment(Number(appointmentId));
|
||||
if (!apt) return res.status(404).json({ error: "Appointment not found" });
|
||||
const patient = apt.patientId ? await storage.getPatient(apt.patientId) : null;
|
||||
if (!patient) return res.status(404).json({ error: "Patient not found" });
|
||||
|
||||
const newClaim = await storage.createClaim({
|
||||
patientId: Number(patient.id),
|
||||
appointmentId: Number(appointmentId),
|
||||
userId: req.user.id,
|
||||
staffId: Number(apt.staffId),
|
||||
patientName: `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim(),
|
||||
memberId: String(patient.insuranceId ?? ""),
|
||||
dateOfBirth: patient.dateOfBirth ? new Date(patient.dateOfBirth) : new Date(),
|
||||
serviceDate: apt.date instanceof Date ? apt.date : new Date(apt.date as any),
|
||||
insuranceProvider: "MassHealth",
|
||||
remarks: "",
|
||||
missingTeethStatus: "No_missing",
|
||||
missingTeeth: {},
|
||||
status: "VOID",
|
||||
} as any);
|
||||
return res.json({ voided: true, claimId: newClaim.id });
|
||||
} catch (err: any) {
|
||||
console.error("void-for-appointment error", err);
|
||||
return res.status(500).json({ error: err.message ?? "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/claims/reset-for-resubmit
|
||||
// Resets the active claim for an appointment back to PENDING with no claimNumber,
|
||||
// so the batch-column will pick it up again on the next run.
|
||||
router.post("/reset-for-resubmit", async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const { appointmentId } = req.body;
|
||||
if (!appointmentId || isNaN(Number(appointmentId))) {
|
||||
return res.status(400).json({ error: "Invalid appointmentId" });
|
||||
}
|
||||
|
||||
const claim = await storage.getActiveClaimByAppointmentId(Number(appointmentId));
|
||||
if (!claim) {
|
||||
return res.json({ reset: false, message: "No existing claim found — will be created fresh on next submit" });
|
||||
}
|
||||
|
||||
await storage.updateClaim(Number(claim.id), { status: "PENDING", claimNumber: null } as any);
|
||||
return res.json({ reset: true, claimId: claim.id });
|
||||
} catch (err: any) {
|
||||
console.error("reset-for-resubmit error", err);
|
||||
return res.status(500).json({ error: err.message ?? "Server error" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user