pdf upload checkpoint1
This commit is contained in:
67
apps/Backend/src/routes/claim-pdf.ts
Normal file
67
apps/Backend/src/routes/claim-pdf.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Router } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import { storage } from "../storage";
|
||||
import { z } from "zod";
|
||||
import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
|
||||
const router = Router();
|
||||
|
||||
|
||||
router.post("/claim-pdf/upload", upload.single("file"), async (req: Request, res: Response) => {
|
||||
const { patientId, claimId } = req.body;
|
||||
const file = req.file;
|
||||
|
||||
if (!file || !patientId) return res.status(400).json({ error: "Missing file or patientId" });
|
||||
|
||||
const created = await storage.createClaimPdf({
|
||||
filename: file.originalname,
|
||||
patientId: parseInt(patientId),
|
||||
claimId: claimId ? parseInt(claimId) : undefined,
|
||||
pdfData: file.buffer,
|
||||
});
|
||||
|
||||
res.json(created);
|
||||
});
|
||||
|
||||
router.get("/claim-pdf/recent", async (req: Request, res: Response) => {
|
||||
const limit = parseInt(req.query.limit as string) || 5;
|
||||
const offset = parseInt(req.query.offset as string) || 0;
|
||||
|
||||
const recent = await storage.getRecentClaimPdfs(limit, offset);
|
||||
res.json(recent);
|
||||
});
|
||||
|
||||
router.get("/claim-pdf/:id", async (req: Request, res: Response) => {
|
||||
const id = parseInt(req.params.id);
|
||||
const pdf = await storage.getClaimPdfById(id);
|
||||
|
||||
if (!pdf) return res.status(404).json({ error: "PDF not found" });
|
||||
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.send(pdf.pdfData);
|
||||
});
|
||||
|
||||
router.delete("/claim-pdf/:id", async (req: Request, res: Response) => {
|
||||
const id = parseInt(req.params.id);
|
||||
const success = await storage.deleteClaimPdf(id);
|
||||
|
||||
res.json({ success });
|
||||
});
|
||||
|
||||
router.put("/claim-pdf/:id", upload.single("file"), async (req: Request, res: Response) => {
|
||||
const id = parseInt(req.params.id);
|
||||
const file = req.file;
|
||||
const claimId = req.body.claimId ? parseInt(req.body.claimId) : undefined;
|
||||
|
||||
const updated = await storage.updateClaimPdf(id, {
|
||||
claimId,
|
||||
filename: file?.originalname,
|
||||
pdfData: file?.buffer,
|
||||
});
|
||||
|
||||
if (!updated) return res.status(404).json({ error: "PDF not found or update failed" });
|
||||
|
||||
res.json(updated);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -6,7 +6,6 @@ import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import multer from "multer";
|
||||
import { forwardToSeleniumAgent, forwardToSeleniumAgent2 } from "../services/seleniumClient";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import axios from "axios";
|
||||
|
||||
const router = Router();
|
||||
@@ -96,6 +95,14 @@ router.post(
|
||||
}
|
||||
|
||||
try{
|
||||
const { patientId, claimId } = req.body; // ✅ Extract patientId from the body
|
||||
|
||||
if (!patientId || !claimId) {
|
||||
return res.status(400).json({ error: "Missing patientId or claimId" });
|
||||
}
|
||||
const parsedPatientId = parseInt(patientId);
|
||||
const parsedClaimId = parseInt(claimId);
|
||||
|
||||
const result = await forwardToSeleniumAgent2();
|
||||
|
||||
if (result.status !== "success") {
|
||||
@@ -104,17 +111,14 @@ router.post(
|
||||
|
||||
const pdfUrl = result.pdf_url;
|
||||
const filename = path.basename(new URL(pdfUrl).pathname);
|
||||
|
||||
const tempDir = path.join(__dirname, "..", "..", "temp");
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
|
||||
const filePath = path.join(tempDir, filename);
|
||||
|
||||
// Download the PDF directly using axios
|
||||
const pdfResponse = await axios.get(pdfUrl, { responseType: "arraybuffer" });
|
||||
fs.writeFileSync(filePath, pdfResponse.data);
|
||||
|
||||
await storage.createClaimPdf(
|
||||
parsedPatientId,
|
||||
parsedClaimId,
|
||||
filename,
|
||||
pdfResponse.data
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
StaffUncheckedCreateInputObjectSchema,
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
InsuranceCredentialUncheckedCreateInputObjectSchema,
|
||||
ClaimPdfUncheckedCreateInputObjectSchema
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -145,6 +146,15 @@ type ClaimWithServiceLines = Claim & {
|
||||
}[];
|
||||
};
|
||||
|
||||
// claim types:
|
||||
type ClaimPdf = z.infer<typeof ClaimPdfUncheckedCreateInputObjectSchema>;
|
||||
|
||||
export interface ClaimPdfMetadata {
|
||||
id: number;
|
||||
filename: string;
|
||||
uploadedAt: Date;
|
||||
}
|
||||
|
||||
export interface IStorage {
|
||||
// User methods
|
||||
getUser(id: number): Promise<User | undefined>;
|
||||
@@ -211,6 +221,27 @@ export interface IStorage {
|
||||
userId: number,
|
||||
siteKey: string
|
||||
): Promise<InsuranceCredential | null>;
|
||||
|
||||
// Claim PDF Methods
|
||||
createClaimPdf(
|
||||
patientId: number,
|
||||
claimId: number,
|
||||
filename: string,
|
||||
pdfData: Buffer
|
||||
): Promise<ClaimPdf>;
|
||||
|
||||
getClaimPdfById(id: number): Promise<ClaimPdf | undefined>;
|
||||
|
||||
getAllClaimPdfs(): Promise<ClaimPdfMetadata[]>;
|
||||
|
||||
getRecentClaimPdfs(limit: number, offset: number): Promise<ClaimPdfMetadata[]>;
|
||||
|
||||
deleteClaimPdf(id: number): Promise<boolean>;
|
||||
|
||||
updateClaimPdf(
|
||||
id: number,
|
||||
updates: Partial<Pick<ClaimPdf, "filename" | "pdfData">>
|
||||
): Promise<ClaimPdf | undefined>;
|
||||
}
|
||||
|
||||
export const storage: IStorage = {
|
||||
@@ -474,5 +505,66 @@ export const storage: IStorage = {
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// pdf claims
|
||||
async createClaimPdf(
|
||||
patientId,
|
||||
claimId,
|
||||
filename,
|
||||
pdfData
|
||||
): Promise<ClaimPdf> {
|
||||
return db.claimPdf.create({
|
||||
data: {
|
||||
patientId,
|
||||
claimId,
|
||||
filename,
|
||||
pdfData,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async getClaimPdfById(id: number): Promise<ClaimPdf | undefined> {
|
||||
const pdf = await db.claimPdf.findUnique({ where: { id } });
|
||||
return pdf ?? undefined;
|
||||
},
|
||||
|
||||
async getAllClaimPdfs(): Promise<ClaimPdfMetadata[]> {
|
||||
return db.claimPdf.findMany({
|
||||
select: { id: true, filename: true, uploadedAt: true },
|
||||
orderBy: { uploadedAt: "desc" },
|
||||
});
|
||||
},
|
||||
|
||||
async getRecentClaimPdfs(limit: number, offset: number): Promise<ClaimPdfMetadata[]> {
|
||||
return db.claimPdf.findMany({
|
||||
skip: offset,
|
||||
take: limit,
|
||||
orderBy: { uploadedAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
filename: true,
|
||||
uploadedAt: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async deleteClaimPdf(id: number): Promise<boolean> {
|
||||
try {
|
||||
await db.claimPdf.delete({ where: { id } });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async updateClaimPdf(
|
||||
id: number,
|
||||
updates: Partial<Pick<ClaimPdf, "filename" | "pdfData">>
|
||||
): Promise<ClaimPdf | undefined> {
|
||||
try {
|
||||
const updated = await db.claimPdf.update({ where: { id }, data: updates });
|
||||
return updated;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
ClaimUncheckedCreateInputObjectSchema
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { AlertCircle, CheckCircle, Clock, FileCheck } from "lucide-react";
|
||||
import { parse, format } from "date-fns";
|
||||
@@ -20,6 +21,7 @@ import { Button } from "@/components/ui/button";
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
@@ -77,6 +79,7 @@ export default function ClaimsPage() {
|
||||
patientId: null,
|
||||
serviceDate: "",
|
||||
});
|
||||
const [claimRes, setClaimRes] = useState<Claim | null>(null);
|
||||
|
||||
// Fetch patients
|
||||
const { data: patients = [], isLoading: isLoadingPatients } = useQuery<
|
||||
@@ -215,8 +218,13 @@ export default function ClaimsPage() {
|
||||
// selenium pdf download handler
|
||||
const handleSeleniumPopup = async (actionType: string) => {
|
||||
try {
|
||||
if (!claimRes?.id || !selectedPatient) {
|
||||
throw new Error("Missing claim or patient selection");
|
||||
}
|
||||
const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", {
|
||||
action: actionType,
|
||||
patientId:selectedPatient,
|
||||
claimId:claimRes.id
|
||||
});
|
||||
const result = await res.json();
|
||||
|
||||
@@ -319,7 +327,8 @@ export default function ClaimsPage() {
|
||||
const res = await apiRequest("POST", "/api/claims/", claimData);
|
||||
return res.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: (data) => {
|
||||
setClaimRes(data);
|
||||
toast({
|
||||
title: "Claim submitted successfully",
|
||||
variant: "default",
|
||||
|
||||
Reference in New Issue
Block a user