pdf upload checkpoint1

This commit is contained in:
2025-06-13 22:59:53 +05:30
parent dd814b902d
commit 56ef5ab65d
9 changed files with 226 additions and 16 deletions

View File

@@ -30,6 +30,11 @@ npm run db:generate
npm run db:migrate
```
- Generate the db types:
```sh
npm run db:generate
```
- seed the db if:
```sh
npm run db:seed

View 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;

View File

@@ -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,

View File

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

View File

@@ -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",

View File

@@ -8,7 +8,7 @@
"lint": "turbo run lint",
"check-types": "turbo run check-types",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"db:generate": "prisma generate --schema=packages/db/prisma/schema.prisma",
"db:generate": "prisma generate --schema=packages/db/prisma/schema.prisma && ts-node packages/db/scripts/patch-zod-buffer.ts",
"db:migrate": "dotenv -e packages/db/.env -- prisma migrate dev --schema=packages/db/prisma/schema.prisma",
"db:seed": "prisma db seed --schema=packages/db/prisma/schema.prisma",
"setup:env": "shx cp packages/db/prisma/.env.example packages/db/prisma/.env && shx cp apps/Frontend/.env.example apps/Frontend/.env && shx cp apps/Backend/.env.example apps/Backend/.env",

View File

@@ -52,6 +52,7 @@ model Patient {
user User @relation(fields: [userId], references: [id])
appointments Appointment[]
claims Claim[]
pdfs ClaimPdf[]
}
model Appointment {
@@ -107,6 +108,7 @@ model Claim {
staff Staff? @relation("ClaimStaff", fields: [staffId], references: [id])
serviceLines ServiceLine[]
pdf ClaimPdf[]
}
model ServiceLine {
@@ -130,6 +132,21 @@ model InsuranceCredential {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, siteKey])
@@index([userId])
@@unique([userId, siteKey])
}
model ClaimPdf {
id Int @id @default(autoincrement())
patientId Int
claimId Int?
filename String
pdfData Bytes
uploadedAt DateTime @default(now())
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
claim Claim? @relation(fields: [claimId], references: [id], onDelete: Cascade)
@@index([patientId])
@@index([claimId])
}

View File

@@ -0,0 +1,15 @@
import fs from 'fs';
import path from 'path';
const dir = path.resolve(__dirname, '../shared/schemas/objects');
fs.readdirSync(dir).forEach(file => {
if (!file.endsWith('.schema.ts')) return;
const full = path.join(dir, file);
let content = fs.readFileSync(full, 'utf8');
if (content.includes('z.instanceof(Buffer)') && !content.includes("import { Buffer")) {
content = `import { Buffer } from 'buffer';\n` + content;
fs.writeFileSync(full, content);
console.log('Patched:', file);
}
});

View File

@@ -4,4 +4,5 @@ export * from '../shared/schemas/objects/PatientUncheckedCreateInput.schema';
export * from '../shared/schemas/objects/UserUncheckedCreateInput.schema';
export * from '../shared/schemas/objects/StaffUncheckedCreateInput.schema'
export * from '../shared/schemas/objects/ClaimUncheckedCreateInput.schema'
export * from '../shared/schemas/objects/InsuranceCredentialUncheckedCreateInput.schema'
export * from '../shared/schemas/objects/InsuranceCredentialUncheckedCreateInput.schema'
export * from '../shared/schemas/objects/ClaimPdfUncheckedCreateInput.schema'