diff --git a/apps/Backend/src/routes/claims.ts b/apps/Backend/src/routes/claims.ts index 284d27c..c8a8a56 100644 --- a/apps/Backend/src/routes/claims.ts +++ b/apps/Backend/src/routes/claims.ts @@ -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 ); } diff --git a/apps/Backend/src/routes/documents.ts b/apps/Backend/src/routes/documents.ts index 4db8cef..04ecb85 100644 --- a/apps/Backend/src/routes/documents.ts +++ b/apps/Backend/src/routes/documents.ts @@ -12,17 +12,18 @@ router.post( "/pdf-groups", async (req: Request, res: Response): Promise => { 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 => { - try { - const idParam = req.params.groupId; +router.get( + "/pdf-files/group/:groupId", + async (req: Request, res: Response): Promise => { + 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" }); diff --git a/apps/Backend/src/routes/index.ts b/apps/Backend/src/routes/index.ts index 98f3394..1a986cc 100644 --- a/apps/Backend/src/routes/index.ts +++ b/apps/Backend/src/routes/index.ts @@ -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); diff --git a/apps/Backend/src/routes/insuranceEligibility.ts b/apps/Backend/src/routes/insuranceEligibility.ts deleted file mode 100644 index 9168026..0000000 --- a/apps/Backend/src/routes/insuranceEligibility.ts +++ /dev/null @@ -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 => { - 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; diff --git a/apps/Backend/src/routes/insuranceStatus.ts b/apps/Backend/src/routes/insuranceStatus.ts new file mode 100644 index 0000000..f0cfbfe --- /dev/null +++ b/apps/Backend/src/routes/insuranceStatus.ts @@ -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 => { + 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 => { + 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; diff --git a/apps/Backend/src/services/seleniumInsuranceClaimStatusClient.ts b/apps/Backend/src/services/seleniumInsuranceClaimStatusClient.ts new file mode 100644 index 0000000..99b2356 --- /dev/null +++ b/apps/Backend/src/services/seleniumInsuranceClaimStatusClient.ts @@ -0,0 +1,27 @@ +import axios from "axios"; + +export interface SeleniumPayload { + data: any; +} + +export async function forwardToSeleniumInsuranceClaimStatusAgent( + insuranceClaimStatusData: any +): Promise { + 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; +} diff --git a/apps/Backend/src/storage/index.ts b/apps/Backend/src/storage/index.ts index 1bb7b59..7ddfaed 100644 --- a/apps/Backend/src/storage/index.ts +++ b/apps/Backend/src/storage/index.ts @@ -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; - findPdfGroupByPatientTitleAndCategory( + findPdfGroupByPatientTitleKeyAndCategory( patientId: number, - title: string, + titleKey: PdfTitle, category: PdfCategory ): Promise; getAllPdfGroups(): Promise; @@ -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 diff --git a/apps/Frontend/src/App.tsx b/apps/Frontend/src/App.tsx index 3e9ccc8..8ffc24c 100644 --- a/apps/Frontend/src/App.tsx +++ b/apps/Frontend/src/App.tsx @@ -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() { } /> } /> } + path="/insurance-status" + component={() => } /> } /> } /> diff --git a/apps/Frontend/src/components/layout/sidebar.tsx b/apps/Frontend/src/components/layout/sidebar.tsx index 8cd143b..1fff026 100644 --- a/apps/Frontend/src/components/layout/sidebar.tsx +++ b/apps/Frontend/src/components/layout/sidebar.tsx @@ -38,7 +38,7 @@ export function Sidebar() { }, { name: "Eligibility/Claim Status", - path: "/eligibility-claim-status", + path: "/insurance-status", icon: , }, { diff --git a/apps/Frontend/src/pages/documents-page.tsx b/apps/Frontend/src/pages/documents-page.tsx index 3df20ca..40d82a0 100644 --- a/apps/Frontend/src/pages/documents-page.tsx +++ b/apps/Frontend/src/pages/documents-page.tsx @@ -137,26 +137,28 @@ export default function DocumentsPage() { {groups.length === 0 ? ( -

+

No groups found for this patient.

) : ( - groups.map((group: any) => ( - - )) +
+ {groups.map((group: any) => ( + + ))} +
)}
diff --git a/apps/Frontend/src/pages/eligibility-claim-status-page.tsx b/apps/Frontend/src/pages/insurance-status-page.tsx similarity index 70% rename from apps/Frontend/src/pages/eligibility-claim-status-page.tsx rename to apps/Frontend/src/pages/insurance-status-page.tsx index 7b4fbdf..54ebb13 100644 --- a/apps/Frontend/src/pages/eligibility-claim-status-page.tsx +++ b/apps/Frontend/src/pages/insurance-status-page.tsx @@ -35,12 +35,14 @@ export default function EligibilityClaimStatusPage() { ); const [selectedPatient, setSelectedPatient] = useState(null); - // Insurance eligibility check form fields + // Insurance eligibility and claim check form fields const [memberId, setMemberId] = useState(""); const [dateOfBirth, setDateOfBirth] = useState(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() { -
+
+ + diff --git a/packages/db/backuppc1.dump b/packages/db/backuppc1.dump new file mode 100644 index 0000000..a3c7742 Binary files /dev/null and b/packages/db/backuppc1.dump differ diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 25464dc..652ab08 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -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 } diff --git a/readmeMigrate.txt b/readmeMigrate.txt new file mode 100644 index 0000000..b1489f7 --- /dev/null +++ b/readmeMigrate.txt @@ -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, \ No newline at end of file