feat(ClaimStatus) - Schema updated, hence changes made in backend, frontend
This commit is contained in:
@@ -125,13 +125,14 @@ router.post(
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
|
||||
const groupTitle = `Insurance Claim`;
|
||||
const groupTitle = "Insurance Claim";
|
||||
const groupTitleKey = "INSURANCE_CLAIM";
|
||||
const groupCategory = "CLAIM";
|
||||
|
||||
// ✅ Find or create PDF group for this claim
|
||||
let group = await storage.findPdfGroupByPatientTitleAndCategory(
|
||||
let group = await storage.findPdfGroupByPatientTitleKeyAndCategory(
|
||||
parsedPatientId,
|
||||
groupTitle,
|
||||
groupTitleKey,
|
||||
groupCategory
|
||||
);
|
||||
|
||||
@@ -139,6 +140,7 @@ router.post(
|
||||
group = await storage.createPdfGroup(
|
||||
parsedPatientId,
|
||||
groupTitle,
|
||||
groupTitleKey,
|
||||
groupCategory
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,17 +12,18 @@ router.post(
|
||||
"/pdf-groups",
|
||||
async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const { title, category, patientId } = req.body;
|
||||
if (!title || !category || !patientId) {
|
||||
const { patientId, groupTitle, groupTitleKey, groupCategory } = req.body;
|
||||
if (!patientId || !groupTitle || groupTitleKey || !groupCategory) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing title, category, or patientId" });
|
||||
.json({ error: "Missing title, titleKey, category, or patientId" });
|
||||
}
|
||||
|
||||
const group = await storage.createPdfGroup(
|
||||
parseInt(patientId),
|
||||
title,
|
||||
category
|
||||
groupTitle,
|
||||
groupTitleKey,
|
||||
groupCategory
|
||||
);
|
||||
|
||||
res.json(group);
|
||||
@@ -89,8 +90,14 @@ router.put(
|
||||
return res.status(400).json({ error: "Missing ID" });
|
||||
}
|
||||
const id = parseInt(idParam);
|
||||
const { title, category } = req.body;
|
||||
const updated = await storage.updatePdfGroup(id, { title, category });
|
||||
const { title, category, titleKey } = req.body;
|
||||
|
||||
const updates: any = {};
|
||||
updates.title = title;
|
||||
updates.category = category;
|
||||
updates.titleKey = titleKey;
|
||||
|
||||
const updated = await storage.updatePdfGroup(id, updates);
|
||||
if (!updated) return res.status(404).json({ error: "Group not found" });
|
||||
res.json(updated);
|
||||
} catch (err) {
|
||||
@@ -144,20 +151,22 @@ router.post(
|
||||
}
|
||||
);
|
||||
|
||||
router.get("/pdf-files/group/:groupId", async (req: Request, res: Response):Promise<any> => {
|
||||
try {
|
||||
const idParam = req.params.groupId;
|
||||
router.get(
|
||||
"/pdf-files/group/:groupId",
|
||||
async (req: Request, res: Response): Promise<any> => {
|
||||
try {
|
||||
const idParam = req.params.groupId;
|
||||
if (!idParam) {
|
||||
return res.status(400).json({ error: "Missing Groupt ID" });
|
||||
}
|
||||
const groupId = parseInt(idParam);
|
||||
const files = await storage.getPdfFilesByGroupId(groupId); // implement this
|
||||
res.json(files);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
const groupId = parseInt(idParam);
|
||||
const files = await storage.getPdfFilesByGroupId(groupId);
|
||||
res.json(files);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/pdf-files/:id",
|
||||
@@ -167,13 +176,109 @@ router.get(
|
||||
if (!idParam) {
|
||||
return res.status(400).json({ error: "Missing ID" });
|
||||
}
|
||||
const id = parseInt(idParam);
|
||||
const pdf = await storage.getPdfFileById(id);
|
||||
if (!pdf || !pdf.pdfData)
|
||||
return res.status(404).json({ error: "PDF not found" });
|
||||
const id = parseInt(idParam, 10);
|
||||
if (Number.isNaN(id)) {
|
||||
return res.status(400).json({ error: "Invalid ID" });
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(pdf.pdfData)) {
|
||||
pdf.pdfData = Buffer.from(Object.values(pdf.pdfData));
|
||||
const pdf = await storage.getPdfFileById(id);
|
||||
if (!pdf || !pdf.pdfData) {
|
||||
return res.status(404).json({ error: "PDF not found" });
|
||||
}
|
||||
|
||||
const data: any = pdf.pdfData;
|
||||
|
||||
// Helper: try many plausible conversions into a Buffer
|
||||
function normalizeToBuffer(d: any): Buffer | null {
|
||||
// Already a Buffer
|
||||
if (Buffer.isBuffer(d)) return d;
|
||||
|
||||
// Uint8Array or other typed arrays
|
||||
if (d instanceof Uint8Array) return Buffer.from(d);
|
||||
|
||||
// ArrayBuffer
|
||||
if (d instanceof ArrayBuffer) return Buffer.from(new Uint8Array(d));
|
||||
|
||||
// number[] (common)
|
||||
if (Array.isArray(d) && d.every((n) => typeof n === "number")) {
|
||||
return Buffer.from(d as number[]);
|
||||
}
|
||||
|
||||
// Some drivers: { data: number[] }
|
||||
if (
|
||||
d &&
|
||||
typeof d === "object" &&
|
||||
Array.isArray(d.data) &&
|
||||
d.data.every((n: any) => typeof n === "number")
|
||||
) {
|
||||
return Buffer.from(d.data as number[]);
|
||||
}
|
||||
|
||||
// Some drivers return object with numeric keys: { '0': 37, '1': 80, ... }
|
||||
if (d && typeof d === "object") {
|
||||
const keys = Object.keys(d);
|
||||
const numericKeys = keys.filter((k) => /^\d+$/.test(k));
|
||||
if (numericKeys.length > 0 && numericKeys.length === keys.length) {
|
||||
// sort numeric keys to correct order and map to numbers
|
||||
const sorted = numericKeys
|
||||
.map((k) => parseInt(k, 10))
|
||||
.sort((a, b) => a - b)
|
||||
.map((n) => d[String(n)]);
|
||||
if (sorted.every((v) => typeof v === "number")) {
|
||||
return Buffer.from(sorted as number[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: if Object.values(d) yields numbers (this is what you used originally)
|
||||
try {
|
||||
const vals = Object.values(d);
|
||||
if (Array.isArray(vals) && vals.every((v) => typeof v === "number")) {
|
||||
// coerce to number[] for TS safety
|
||||
return Buffer.from(vals as number[]);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// give up
|
||||
return null;
|
||||
}
|
||||
|
||||
const pdfBuffer = normalizeToBuffer(data);
|
||||
|
||||
if (!pdfBuffer) {
|
||||
console.error("Unsupported pdf.pdfData shape:", {
|
||||
typeofData: typeof data,
|
||||
constructorName:
|
||||
data && data.constructor ? data.constructor.name : undefined,
|
||||
keys:
|
||||
data && typeof data === "object"
|
||||
? Object.keys(data).slice(0, 20)
|
||||
: undefined,
|
||||
sample: (() => {
|
||||
if (Array.isArray(data)) return data.slice(0, 20);
|
||||
if (data && typeof data === "object") {
|
||||
const vals = Object.values(data);
|
||||
return Array.isArray(vals) ? vals.slice(0, 20) : undefined;
|
||||
}
|
||||
return String(data).slice(0, 200);
|
||||
})(),
|
||||
});
|
||||
|
||||
// Try a safe textual fallback (may produce invalid PDF but avoids crashing)
|
||||
try {
|
||||
const fallback = Buffer.from(String(data));
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="${pdf.filename}"; filename*=UTF-8''${encodeURIComponent(pdf.filename)}`
|
||||
);
|
||||
return res.send(fallback);
|
||||
} catch (err) {
|
||||
console.error("Failed fallback conversion:", err);
|
||||
return res.status(500).json({ error: "Cannot process PDF data" });
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
@@ -181,7 +286,7 @@ router.get(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="${pdf.filename}"; filename*=UTF-8''${encodeURIComponent(pdf.filename)}`
|
||||
);
|
||||
res.send(pdf.pdfData);
|
||||
res.send(pdfBuffer);
|
||||
} catch (err) {
|
||||
console.error("Error downloading PDF file:", err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
|
||||
@@ -7,7 +7,7 @@ import claimsRoutes from "./claims";
|
||||
import patientDataExtractionRoutes from "./patientDataExtraction";
|
||||
import insuranceCredsRoutes from "./insuranceCreds";
|
||||
import documentsRoutes from "./documents";
|
||||
import insuranceEligibilityRoutes from "./insuranceEligibility";
|
||||
import insuranceStatusRoutes from "./insuranceStatus";
|
||||
import paymentsRoutes from "./payments";
|
||||
import databaseManagementRoutes from "./database-management";
|
||||
import notificationsRoutes from "./notifications";
|
||||
@@ -23,7 +23,7 @@ router.use("/patientDataExtraction", patientDataExtractionRoutes);
|
||||
router.use("/claims", claimsRoutes);
|
||||
router.use("/insuranceCreds", insuranceCredsRoutes);
|
||||
router.use("/documents", documentsRoutes);
|
||||
router.use("/insuranceEligibility", insuranceEligibilityRoutes);
|
||||
router.use("/insurance-status", insuranceStatusRoutes);
|
||||
router.use("/payments", paymentsRoutes);
|
||||
router.use("/database-management", databaseManagementRoutes);
|
||||
router.use("/notifications", notificationsRoutes);
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import { Router } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import { storage } from "../storage";
|
||||
import { forwardToSeleniumInsuranceEligibilityAgent } from "../services/seleniumInsuranceEligibilityClient";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post("/check", async (req: Request, res: Response): Promise<any> => {
|
||||
if (!req.body.data) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing Insurance Eligibility data for selenium" });
|
||||
}
|
||||
|
||||
if (!req.user || !req.user.id) {
|
||||
return res.status(401).json({ error: "Unauthorized: user info missing" });
|
||||
}
|
||||
|
||||
try {
|
||||
const insuranceEligibilityData = JSON.parse(req.body.data);
|
||||
|
||||
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(
|
||||
req.user.id,
|
||||
insuranceEligibilityData.insuranceSiteKey
|
||||
);
|
||||
if (!credentials) {
|
||||
return res.status(404).json({
|
||||
error:
|
||||
"No insurance credentials found for this provider, Kindly Update this at Settings Page.",
|
||||
});
|
||||
}
|
||||
|
||||
const enrichedData = {
|
||||
...insuranceEligibilityData,
|
||||
massdhpUsername: credentials.username,
|
||||
massdhpPassword: credentials.password,
|
||||
};
|
||||
|
||||
const result =
|
||||
await forwardToSeleniumInsuranceEligibilityAgent(enrichedData);
|
||||
|
||||
// ✅ Step 1: Check result and update patient status
|
||||
const patient = await storage.getPatientByInsuranceId(
|
||||
insuranceEligibilityData.memberId
|
||||
);
|
||||
|
||||
if (patient && patient.id !== undefined) {
|
||||
const newStatus = result.eligibility === "Y" ? "active" : "inactive";
|
||||
await storage.updatePatient(patient.id, { status: newStatus });
|
||||
result.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
|
||||
// ✅ Step 2: Handle PDF Upload
|
||||
if (result.pdf_path && result.pdf_path.endsWith(".pdf")) {
|
||||
const pdfBuffer = await fs.readFile(result.pdf_path);
|
||||
|
||||
const groupTitle = "Eligibility PDFs";
|
||||
const groupCategory = "ELIGIBILITY";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleAndCategory(
|
||||
patient.id,
|
||||
groupTitle,
|
||||
groupCategory
|
||||
);
|
||||
|
||||
// Step 2b: Create group if it doesn’t exist
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(
|
||||
patient.id,
|
||||
groupTitle,
|
||||
groupCategory
|
||||
);
|
||||
}
|
||||
|
||||
if (!group?.id) {
|
||||
throw new Error("PDF group creation failed: missing group ID");
|
||||
}
|
||||
await storage.createPdfFile(
|
||||
group.id,
|
||||
path.basename(result.pdf_path),
|
||||
pdfBuffer
|
||||
);
|
||||
|
||||
await fs.unlink(result.pdf_path);
|
||||
|
||||
result.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
} else {
|
||||
result.pdfUploadStatus =
|
||||
"No valid PDF path provided by Selenium, Couldn't upload pdf to server.";
|
||||
}
|
||||
} else {
|
||||
result.patientUpdateStatus =
|
||||
"Patient not found or missing ID; no update performed";
|
||||
}
|
||||
|
||||
res.json({
|
||||
patientUpdateStatus: result.patientUpdateStatus,
|
||||
pdfUploadStatus: result.pdfUploadStatus,
|
||||
});
|
||||
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
return res.status(500).json({
|
||||
error: err.message || "Failed to forward to selenium agent",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
212
apps/Backend/src/routes/insuranceStatus.ts
Normal file
212
apps/Backend/src/routes/insuranceStatus.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { Router } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import { storage } from "../storage";
|
||||
import { forwardToSeleniumInsuranceEligibilityAgent } from "../services/seleniumInsuranceEligibilityClient";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { forwardToSeleniumInsuranceClaimStatusAgent } from "../services/seleniumInsuranceClaimStatusClient";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post("/eligibility-check", async (req: Request, res: Response): Promise<any> => {
|
||||
if (!req.body.data) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing Insurance Eligibility data for selenium" });
|
||||
}
|
||||
|
||||
if (!req.user || !req.user.id) {
|
||||
return res.status(401).json({ error: "Unauthorized: user info missing" });
|
||||
}
|
||||
|
||||
try {
|
||||
const insuranceEligibilityData = JSON.parse(req.body.data);
|
||||
|
||||
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(
|
||||
req.user.id,
|
||||
insuranceEligibilityData.insuranceSiteKey
|
||||
);
|
||||
if (!credentials) {
|
||||
return res.status(404).json({
|
||||
error:
|
||||
"No insurance credentials found for this provider, Kindly Update this at Settings Page.",
|
||||
});
|
||||
}
|
||||
|
||||
const enrichedData = {
|
||||
...insuranceEligibilityData,
|
||||
massdhpUsername: credentials.username,
|
||||
massdhpPassword: credentials.password,
|
||||
};
|
||||
|
||||
const result =
|
||||
await forwardToSeleniumInsuranceEligibilityAgent(enrichedData);
|
||||
|
||||
// ✅ Step 1: Check result and update patient status
|
||||
const patient = await storage.getPatientByInsuranceId(
|
||||
insuranceEligibilityData.memberId
|
||||
);
|
||||
|
||||
if (patient && patient.id !== undefined) {
|
||||
const newStatus = result.eligibility === "Y" ? "active" : "inactive";
|
||||
await storage.updatePatient(patient.id, { status: newStatus });
|
||||
result.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
|
||||
// ✅ Step 2: Handle PDF Upload
|
||||
if (result.pdf_path && result.pdf_path.endsWith(".pdf")) {
|
||||
const pdfBuffer = await fs.readFile(result.pdf_path);
|
||||
|
||||
const groupTitle = "Insurance Status PDFs";
|
||||
const groupTitleKey = "INSURANCE_STATUS_PDFs"
|
||||
const groupCategory = "ELIGIBILITY_STATUS";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKeyAndCategory(
|
||||
patient.id,
|
||||
groupTitleKey,
|
||||
groupCategory
|
||||
);
|
||||
|
||||
// Step 2b: Create group if it doesn’t exist
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(
|
||||
patient.id,
|
||||
groupTitle,
|
||||
groupTitleKey,
|
||||
groupCategory
|
||||
);
|
||||
}
|
||||
|
||||
if (!group?.id) {
|
||||
throw new Error("PDF group creation failed: missing group ID");
|
||||
}
|
||||
await storage.createPdfFile(
|
||||
group.id,
|
||||
path.basename(result.pdf_path),
|
||||
pdfBuffer
|
||||
);
|
||||
|
||||
await fs.unlink(result.pdf_path);
|
||||
|
||||
result.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
} else {
|
||||
result.pdfUploadStatus =
|
||||
"No valid PDF path provided by Selenium, Couldn't upload pdf to server.";
|
||||
}
|
||||
} else {
|
||||
result.patientUpdateStatus =
|
||||
"Patient not found or missing ID; no update performed";
|
||||
}
|
||||
|
||||
res.json({
|
||||
patientUpdateStatus: result.patientUpdateStatus,
|
||||
pdfUploadStatus: result.pdfUploadStatus,
|
||||
});
|
||||
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
return res.status(500).json({
|
||||
error: err.message || "Failed to forward to selenium agent",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/claim-status-check", async (req: Request, res: Response): Promise<any> => {
|
||||
if (!req.body.data) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Missing Insurance Status data for selenium" });
|
||||
}
|
||||
|
||||
if (!req.user || !req.user.id) {
|
||||
return res.status(401).json({ error: "Unauthorized: user info missing" });
|
||||
}
|
||||
|
||||
try {
|
||||
const insuranceClaimStatusData = JSON.parse(req.body.data);
|
||||
|
||||
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(
|
||||
req.user.id,
|
||||
insuranceClaimStatusData.insuranceSiteKey
|
||||
);
|
||||
if (!credentials) {
|
||||
return res.status(404).json({
|
||||
error:
|
||||
"No insurance credentials found for this provider, Kindly Update this at Settings Page.",
|
||||
});
|
||||
}
|
||||
|
||||
const enrichedData = {
|
||||
...insuranceClaimStatusData,
|
||||
massdhpUsername: credentials.username,
|
||||
massdhpPassword: credentials.password,
|
||||
};
|
||||
|
||||
const result =
|
||||
await forwardToSeleniumInsuranceClaimStatusAgent(enrichedData);
|
||||
|
||||
|
||||
// ✅ Step 1: Check result
|
||||
const patient = await storage.getPatientByInsuranceId(
|
||||
insuranceClaimStatusData.memberId
|
||||
);
|
||||
|
||||
if (patient && patient.id !== undefined) {
|
||||
// ✅ Step 2: Handle PDF Upload
|
||||
if (result.pdf_path && result.pdf_path.endsWith(".pdf")) {
|
||||
const pdfBuffer = await fs.readFile(result.pdf_path);
|
||||
|
||||
const groupTitle = "Insurance Status PDFs";
|
||||
const groupTitleKey = "INSURANCE_STATUS_PDFs"
|
||||
const groupCategory = "CLAIM_STATUS";
|
||||
|
||||
let group = await storage.findPdfGroupByPatientTitleKeyAndCategory(
|
||||
patient.id,
|
||||
groupTitleKey,
|
||||
groupCategory
|
||||
);
|
||||
|
||||
// Step 2b: Create group if it doesn’t exist
|
||||
if (!group) {
|
||||
group = await storage.createPdfGroup(
|
||||
patient.id,
|
||||
groupTitle,
|
||||
groupTitleKey,
|
||||
groupCategory
|
||||
);
|
||||
}
|
||||
|
||||
if (!group?.id) {
|
||||
throw new Error("PDF group creation failed: missing group ID");
|
||||
}
|
||||
await storage.createPdfFile(
|
||||
group.id,
|
||||
path.basename(result.pdf_path),
|
||||
pdfBuffer
|
||||
);
|
||||
|
||||
await fs.unlink(result.pdf_path);
|
||||
|
||||
result.pdfUploadStatus = `PDF saved to group: ${group.title}`;
|
||||
} else {
|
||||
result.pdfUploadStatus =
|
||||
"No valid PDF path provided by Selenium, Couldn't upload pdf to server.";
|
||||
}
|
||||
} else {
|
||||
result.patientUpdateStatus =
|
||||
"Patient not found or missing ID; no update performed";
|
||||
}
|
||||
|
||||
res.json({
|
||||
patientUpdateStatus: result.patientUpdateStatus,
|
||||
pdfUploadStatus: result.pdfUploadStatus,
|
||||
});
|
||||
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
return res.status(500).json({
|
||||
error: err.message || "Failed to forward to selenium agent",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,27 @@
|
||||
import axios from "axios";
|
||||
|
||||
export interface SeleniumPayload {
|
||||
data: any;
|
||||
}
|
||||
|
||||
export async function forwardToSeleniumInsuranceClaimStatusAgent(
|
||||
insuranceClaimStatusData: any
|
||||
): Promise<any> {
|
||||
const payload: SeleniumPayload = {
|
||||
data: insuranceClaimStatusData,
|
||||
};
|
||||
|
||||
const result = await axios.post(
|
||||
"http://localhost:5002/claim-status-check",
|
||||
payload
|
||||
);
|
||||
if (result.data.status === "error") {
|
||||
const errorMsg =
|
||||
typeof result.data.message === "string"
|
||||
? result.data.message
|
||||
: result.data.message?.msg || "Selenium agent error";
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
import { PdfCategory } from "@repo/db/generated/prisma";
|
||||
import { PdfCategory, PdfTitle } from "@repo/db/generated/prisma";
|
||||
import {
|
||||
Appointment,
|
||||
Claim,
|
||||
@@ -162,11 +162,12 @@ export interface IStorage {
|
||||
createPdfGroup(
|
||||
patientId: number,
|
||||
title: string,
|
||||
titleKey: PdfTitle,
|
||||
category: PdfCategory
|
||||
): Promise<PdfGroup>;
|
||||
findPdfGroupByPatientTitleAndCategory(
|
||||
findPdfGroupByPatientTitleKeyAndCategory(
|
||||
patientId: number,
|
||||
title: string,
|
||||
titleKey: PdfTitle,
|
||||
category: PdfCategory
|
||||
): Promise<PdfGroup | undefined>;
|
||||
getAllPdfGroups(): Promise<PdfGroup[]>;
|
||||
@@ -738,22 +739,27 @@ export const storage: IStorage = {
|
||||
// PdfGroup CRUD
|
||||
// ----------------------
|
||||
|
||||
async createPdfGroup(patientId, title, category) {
|
||||
async createPdfGroup(patientId, title, titleKey, category) {
|
||||
return db.pdfGroup.create({
|
||||
data: {
|
||||
patientId,
|
||||
title,
|
||||
titleKey,
|
||||
category,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async findPdfGroupByPatientTitleAndCategory(patientId, title, category) {
|
||||
async findPdfGroupByPatientTitleKeyAndCategory(
|
||||
patientId,
|
||||
titleKey,
|
||||
category
|
||||
) {
|
||||
return (
|
||||
(await db.pdfGroup.findFirst({
|
||||
where: {
|
||||
patientId,
|
||||
title,
|
||||
titleKey,
|
||||
category,
|
||||
},
|
||||
})) ?? undefined
|
||||
|
||||
@@ -17,8 +17,8 @@ const PatientsPage = lazy(() => import("./pages/patients-page"));
|
||||
const SettingsPage = lazy(() => import("./pages/settings-page"));
|
||||
const ClaimsPage = lazy(() => import("./pages/claims-page"));
|
||||
const PaymentsPage = lazy(() => import("./pages/payments-page"));
|
||||
const EligibilityClaimStatusPage = lazy(
|
||||
() => import("./pages/eligibility-claim-status-page")
|
||||
const InsuranceStatusPage = lazy(
|
||||
() => import("./pages/insurance-status-page")
|
||||
);
|
||||
const DocumentPage = lazy(() => import("./pages/documents-page"));
|
||||
const DatabaseManagementPage = lazy(
|
||||
@@ -41,8 +41,8 @@ function Router() {
|
||||
<ProtectedRoute path="/settings" component={() => <SettingsPage />} />
|
||||
<ProtectedRoute path="/claims" component={() => <ClaimsPage />} />
|
||||
<ProtectedRoute
|
||||
path="/eligibility-claim-status"
|
||||
component={() => <EligibilityClaimStatusPage />}
|
||||
path="/insurance-status"
|
||||
component={() => <InsuranceStatusPage />}
|
||||
/>
|
||||
<ProtectedRoute path="/payments" component={() => <PaymentsPage />} />
|
||||
<ProtectedRoute path="/documents" component={() => <DocumentPage />} />
|
||||
|
||||
@@ -38,7 +38,7 @@ export function Sidebar() {
|
||||
},
|
||||
{
|
||||
name: "Eligibility/Claim Status",
|
||||
path: "/eligibility-claim-status",
|
||||
path: "/insurance-status",
|
||||
icon: <Shield className="h-5 w-5" />,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -137,26 +137,28 @@ export default function DocumentsPage() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{groups.length === 0 ? (
|
||||
<p className="text-muted-foreground">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No groups found for this patient.
|
||||
</p>
|
||||
) : (
|
||||
groups.map((group: any) => (
|
||||
<Button
|
||||
key={group.id}
|
||||
variant={
|
||||
group.id === selectedGroupId ? "default" : "outline"
|
||||
}
|
||||
onClick={() =>
|
||||
setSelectedGroupId((prevId) =>
|
||||
prevId === group.id ? null : group.id
|
||||
)
|
||||
}
|
||||
>
|
||||
<FolderOpen className="w-4 h-4 mr-2" />
|
||||
Group - {group.title}
|
||||
</Button>
|
||||
))
|
||||
<div className="flex flex-col gap-2">
|
||||
{groups.map((group: any) => (
|
||||
<Button
|
||||
key={group.id}
|
||||
variant={
|
||||
group.id === selectedGroupId ? "default" : "outline"
|
||||
}
|
||||
onClick={() =>
|
||||
setSelectedGroupId((prevId) =>
|
||||
prevId === group.id ? null : group.id
|
||||
)
|
||||
}
|
||||
>
|
||||
<FolderOpen className="w-4 h-4 mr-2" />
|
||||
Group - {group.title}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -35,12 +35,14 @@ export default function EligibilityClaimStatusPage() {
|
||||
);
|
||||
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
|
||||
|
||||
// Insurance eligibility check form fields
|
||||
// Insurance eligibility and claim check form fields
|
||||
const [memberId, setMemberId] = useState("");
|
||||
const [dateOfBirth, setDateOfBirth] = useState<Date | null>(null);
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [isCheckingEligibility, setIsCheckingEligibility] = useState(false);
|
||||
const [isCheckingEligibilityStatus, setIsCheckingEligibilityStatus] =
|
||||
useState(false);
|
||||
const [isCheckingClaimStatus, setIsCheckingClaimStatus] = useState(false);
|
||||
|
||||
// Populate fields from selected patient
|
||||
useEffect(() => {
|
||||
@@ -95,8 +97,8 @@ export default function EligibilityClaimStatusPage() {
|
||||
},
|
||||
});
|
||||
|
||||
// handle selenium
|
||||
const handleSelenium = async () => {
|
||||
// handle eligibility selenium
|
||||
const handleEligibilityCheckSelenium = async () => {
|
||||
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
|
||||
|
||||
const data = {
|
||||
@@ -113,7 +115,7 @@ export default function EligibilityClaimStatusPage() {
|
||||
);
|
||||
const response = await apiRequest(
|
||||
"POST",
|
||||
"/api/insuranceEligibility/check",
|
||||
"/api/insurance-status/eligibility-check",
|
||||
{ data: JSON.stringify(data) }
|
||||
);
|
||||
const result = await response.json();
|
||||
@@ -148,6 +150,59 @@ export default function EligibilityClaimStatusPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// Claim Status Check Selenium
|
||||
const handleStatusCheckSelenium = async () => {
|
||||
const formattedDob = dateOfBirth ? formatLocalDate(dateOfBirth) : "";
|
||||
|
||||
const data = {
|
||||
memberId,
|
||||
dateOfBirth: formattedDob,
|
||||
insuranceSiteKey: "MH",
|
||||
};
|
||||
try {
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
status: "pending",
|
||||
message: "Sending Data to Selenium...",
|
||||
})
|
||||
);
|
||||
const response = await apiRequest(
|
||||
"POST",
|
||||
"/api/insurance-status/claim-status-check",
|
||||
{ data: JSON.stringify(data) }
|
||||
);
|
||||
const result = await response.json();
|
||||
if (result.error) throw new Error(result.error);
|
||||
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
status: "success",
|
||||
message:
|
||||
"Claim status is updated, and its pdf is uploaded at Document Page.",
|
||||
})
|
||||
);
|
||||
|
||||
toast({
|
||||
title: "Selenium service done.",
|
||||
description:
|
||||
"Your Claim Status is fetched and updated, Kindly search through the patient.",
|
||||
variant: "default",
|
||||
});
|
||||
} catch (error: any) {
|
||||
dispatch(
|
||||
setTaskStatus({
|
||||
status: "error",
|
||||
message: error.message || "Selenium submission failed",
|
||||
})
|
||||
);
|
||||
toast({
|
||||
title: "Selenium service error",
|
||||
description: error.message || "An error occurred.",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddPatient = async () => {
|
||||
const newPatient: InsertPatient = {
|
||||
firstName,
|
||||
@@ -162,8 +217,8 @@ export default function EligibilityClaimStatusPage() {
|
||||
await addPatientMutation.mutateAsync(newPatient);
|
||||
};
|
||||
|
||||
// Handle insurance provider button clicks
|
||||
const handleMHButton = async () => {
|
||||
// Handle insurance provider eligibility button clicks
|
||||
const handleMHEligibilityButton = async () => {
|
||||
// Form Fields check
|
||||
if (!memberId || !dateOfBirth || !firstName) {
|
||||
toast({
|
||||
@@ -175,7 +230,7 @@ export default function EligibilityClaimStatusPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCheckingEligibility(true);
|
||||
setIsCheckingEligibilityStatus(true);
|
||||
|
||||
// Adding patient if same patient exists then it will skip.
|
||||
try {
|
||||
@@ -183,11 +238,40 @@ export default function EligibilityClaimStatusPage() {
|
||||
await handleAddPatient();
|
||||
}
|
||||
|
||||
await handleSelenium();
|
||||
await handleEligibilityCheckSelenium();
|
||||
|
||||
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
} finally {
|
||||
setIsCheckingEligibility(false);
|
||||
setIsCheckingEligibilityStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle insurance provider Status Check button clicks
|
||||
const handleMHStatusButton = async () => {
|
||||
// Form Fields check
|
||||
if (!memberId || !dateOfBirth || !firstName) {
|
||||
toast({
|
||||
title: "Missing Fields",
|
||||
description:
|
||||
"Please fill in all the required fields: Member ID, Date of Birth, First Name.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCheckingClaimStatus(true);
|
||||
|
||||
// Adding patient if same patient exists then it will skip.
|
||||
try {
|
||||
if (!selectedPatient) {
|
||||
await handleAddPatient();
|
||||
}
|
||||
|
||||
await handleStatusCheckSelenium();
|
||||
|
||||
await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE });
|
||||
} finally {
|
||||
setIsCheckingClaimStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -258,13 +342,13 @@ export default function EligibilityClaimStatusPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex flex-col-2 gap-4">
|
||||
<Button
|
||||
onClick={() => handleMHButton()}
|
||||
onClick={() => handleMHEligibilityButton()}
|
||||
className="w-full"
|
||||
disabled={isCheckingEligibility}
|
||||
disabled={isCheckingEligibilityStatus}
|
||||
>
|
||||
{isCheckingEligibility ? (
|
||||
{isCheckingEligibilityStatus ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
@@ -272,7 +356,25 @@ export default function EligibilityClaimStatusPage() {
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH Eligibility
|
||||
MH Eligibility Check
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => handleMHStatusButton()}
|
||||
className="w-full"
|
||||
disabled={isCheckingClaimStatus}
|
||||
>
|
||||
{isCheckingClaimStatus ? (
|
||||
<>
|
||||
<LoaderCircleIcon className="h-4 w-4 mr-2 animate-spin" />
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="h-4 w-4 mr-2" />
|
||||
MH Status Check
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
BIN
packages/db/backuppc1.dump
Normal file
BIN
packages/db/backuppc1.dump
Normal file
Binary file not shown.
@@ -164,10 +164,10 @@ enum ServiceLineStatus {
|
||||
}
|
||||
|
||||
model ClaimFile {
|
||||
id Int @id @default(autoincrement())
|
||||
claimId Int
|
||||
filename String
|
||||
mimeType String
|
||||
id Int @id @default(autoincrement())
|
||||
claimId Int
|
||||
filename String
|
||||
mimeType String
|
||||
|
||||
claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
@@ -187,7 +187,8 @@ model InsuranceCredential {
|
||||
|
||||
model PdfGroup {
|
||||
id Int @id @default(autoincrement())
|
||||
title String // e.g., "June Claim Docs", "Eligibility Set A"
|
||||
title String
|
||||
titleKey PdfTitle?
|
||||
category PdfCategory
|
||||
createdAt DateTime @default(now())
|
||||
patientId Int
|
||||
@@ -196,6 +197,7 @@ model PdfGroup {
|
||||
|
||||
@@index([patientId])
|
||||
@@index([category])
|
||||
@@index([titleKey])
|
||||
}
|
||||
|
||||
model PdfFile {
|
||||
@@ -209,9 +211,16 @@ model PdfFile {
|
||||
@@index([groupId])
|
||||
}
|
||||
|
||||
enum PdfTitle {
|
||||
INSURANCE_CLAIM
|
||||
INSURANCE_STATUS_PDFs
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum PdfCategory {
|
||||
CLAIM
|
||||
ELIGIBILITY
|
||||
ELIGIBILITY_STATUS
|
||||
CLAIM_STATUS
|
||||
OTHER
|
||||
}
|
||||
|
||||
|
||||
135
readmeMigrate.txt
Normal file
135
readmeMigrate.txt
Normal file
@@ -0,0 +1,135 @@
|
||||
1. get db backup
|
||||
|
||||
2.
|
||||
npx prisma migrate dev --create-only --name pdfgroup_titlekey_setup
|
||||
|
||||
3. paste this code in migration file:
|
||||
|
||||
```
|
||||
-- migration: pdfgroup_titlekey_setup
|
||||
BEGIN;
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- 1) Create PdfTitle enum type (if not exists) and add nullable column
|
||||
------------------------------------------------------------------------
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_type t
|
||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'PdfTitle'
|
||||
) THEN
|
||||
CREATE TYPE "PdfTitle" AS ENUM (
|
||||
'INSURANCE_CLAIM',
|
||||
'INSURANCE_STATUS_PDFs',
|
||||
'OTHER'
|
||||
);
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
ALTER TABLE "PdfGroup"
|
||||
ADD COLUMN IF NOT EXISTS "titleKey" "PdfTitle";
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- 2) Populate titleKey and rename title values
|
||||
-- - 'Insurance Claim' -> titleKey = INSURANCE_CLAIM
|
||||
-- - 'Eligibility PDFs' -> titleKey = INSURANCE_STATUS_PDFs and title -> 'Insurance Status PDFs'
|
||||
------------------------------------------------------------------------
|
||||
UPDATE "PdfGroup"
|
||||
SET "titleKey" = 'INSURANCE_CLAIM'
|
||||
WHERE TRIM("title") = 'Insurance Claim';
|
||||
|
||||
UPDATE "PdfGroup"
|
||||
SET
|
||||
"titleKey" = 'INSURANCE_STATUS_PDFs',
|
||||
"title" = 'Insurance Status PDFs'
|
||||
WHERE TRIM("title") = 'Eligibility PDFs';
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- 3) Safely replace PdfCategory enum values:
|
||||
-- Strategy:
|
||||
-- a) change column type to text
|
||||
-- b) normalize existing text values (map legacy names -> new names)
|
||||
-- c) create new enum type with desired values
|
||||
-- d) cast column from text -> new enum
|
||||
-- e) drop old enum type and rename new enum to PdfCategory
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- a) Convert column to text (so we can freely rewrite strings)
|
||||
ALTER TABLE "PdfGroup"
|
||||
ALTER COLUMN "category" TYPE text
|
||||
USING "category"::text;
|
||||
|
||||
-- b) Normalize the textual values
|
||||
-- mapping rules:
|
||||
-- 'ELIGIBILITY' -> 'ELIGIBILITY_STATUS'
|
||||
-- 'CLAIM' -> 'CLAIM'
|
||||
-- 'OTHER' -> 'OTHER'
|
||||
-- 'CLAIM_STATUS'-> 'CLAIM_STATUS' (if somehow present as text)
|
||||
-- Any unknown legacy values will be coerced to 'OTHER'
|
||||
UPDATE "PdfGroup"
|
||||
SET "category" =
|
||||
CASE
|
||||
WHEN LOWER(TRIM("category")) = 'eligibility' THEN 'ELIGIBILITY_STATUS'
|
||||
WHEN LOWER(TRIM("category")) = 'claim' THEN 'CLAIM'
|
||||
WHEN LOWER(TRIM("category")) = 'other' THEN 'OTHER'
|
||||
WHEN LOWER(TRIM("category")) = 'claim_status' THEN 'CLAIM_STATUS'
|
||||
WHEN LOWER(TRIM("category")) = 'claim status' THEN 'CLAIM_STATUS'
|
||||
ELSE 'OTHER'
|
||||
END;
|
||||
|
||||
-- c) Create the new enum type (with a temporary name)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_type t
|
||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'PdfCategory_new'
|
||||
) THEN
|
||||
CREATE TYPE "PdfCategory_new" AS ENUM (
|
||||
'CLAIM',
|
||||
'ELIGIBILITY_STATUS',
|
||||
'CLAIM_STATUS',
|
||||
'OTHER'
|
||||
);
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
-- d) Cast the column from text to the new enum
|
||||
ALTER TABLE "PdfGroup"
|
||||
ALTER COLUMN "category" TYPE "PdfCategory_new"
|
||||
USING ("category")::"PdfCategory_new";
|
||||
|
||||
-- e) Drop the old enum type (if present) and rename the new type to PdfCategory.
|
||||
-- First drop old type if it exists
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||
WHERE t.typname = 'PdfCategory'
|
||||
) THEN
|
||||
EXECUTE 'DROP TYPE "PdfCategory"';
|
||||
END IF;
|
||||
|
||||
-- rename new to canonical name
|
||||
EXECUTE 'ALTER TYPE "PdfCategory_new" RENAME TO "PdfCategory"';
|
||||
END$$;
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- 4) Ensure indexes requested exist
|
||||
------------------------------------------------------------------------
|
||||
CREATE INDEX IF NOT EXISTS "PdfGroup_patientId_idx" ON "PdfGroup" ("patientId");
|
||||
CREATE INDEX IF NOT EXISTS "PdfGroup_category_idx" ON "PdfGroup" ("category");
|
||||
CREATE INDEX IF NOT EXISTS "PdfGroup_titleKey_idx" ON "PdfGroup" ("titleKey");
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
|
||||
4. apply migrate:
|
||||
npx prisma migrate dev
|
||||
|
||||
|
||||
done,
|
||||
Reference in New Issue
Block a user