diff --git a/apps/Backend/src/routes/claims.ts b/apps/Backend/src/routes/claims.ts new file mode 100644 index 0000000..3e222cd --- /dev/null +++ b/apps/Backend/src/routes/claims.ts @@ -0,0 +1,159 @@ +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(); + +// Define Zod schemas +const ClaimSchema = ( + ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + id: true, + createdAt: true, + updatedAt: true, +}); + +type InsertClaim = z.infer; + +const updateClaimSchema = ( + ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject +) + .omit({ + id: true, + createdAt: true, + updatedAt: true, + }) + .partial(); + +type UpdateClaim = z.infer; + +// Routes + +// Get all claims for the logged-in user +router.get("/", async (req: Request, res: Response) => { + try { + const claims = await storage.getClaimsByUserId(req.user!.id); + res.json(claims); + } catch (error) { + res.status(500).json({ message: "Failed to retrieve claims" }); + } +}); + +// Get a single claim by ID +router.get("/:id", async (req: Request, res: Response): Promise => { + try { + const idParam = req.params.id; + if (!idParam) { + return res.status(400).json({ error: "Missing claim ID" }); + } + const claimId = parseInt(idParam, 10); + if (isNaN(claimId)) { + return res.status(400).json({ error: "Invalid claim ID" }); + } + + const claim = await storage.getClaim(claimId); + if (!claim) { + return res.status(404).json({ message: "Claim not found" }); + } + + if (claim.userId !== req.user!.id) { + return res.status(403).json({ message: "Forbidden" }); + } + + res.json(claim); + } catch (error) { + res.status(500).json({ message: "Failed to retrieve claim" }); + } +}); + +// Create a new claim +router.post("/", async (req: Request, res: Response): Promise => { + try { + const claimData = ClaimSchema.parse({ + ...req.body, + userId: req.user!.id, + }); + + const newClaim = await storage.createClaim(claimData); + res.status(201).json(newClaim); + } catch (error) { + if (error instanceof z.ZodError) { + return res.status(400).json({ + message: "Validation error", + errors: error.format(), + }); + } + res.status(500).json({ message: "Failed to create claim" }); + } +}); + +// Update a claim +router.put("/:id", async (req: Request, res: Response): Promise => { + try { + const idParam = req.params.id; + if (!idParam) { + return res.status(400).json({ error: "Missing claim ID" }); + } + + const claimId = parseInt(idParam, 10); + if (isNaN(claimId)) { + return res.status(400).json({ error: "Invalid claim ID" }); + } + + const existingClaim = await storage.getClaim(claimId); + if (!existingClaim) { + return res.status(404).json({ message: "Claim not found" }); + } + + if (existingClaim.userId !== req.user!.id) { + return res.status(403).json({ message: "Forbidden" }); + } + + const claimData = updateClaimSchema.parse(req.body); + const updatedClaim = await storage.updateClaim(claimId, claimData); + res.json(updatedClaim); + } catch (error) { + if (error instanceof z.ZodError) { + return res.status(400).json({ + message: "Validation error", + errors: error.format(), + }); + } + res.status(500).json({ message: "Failed to update claim" }); + } +}); + +// Delete a claim +router.delete("/:id", async (req: Request, res: Response): Promise => { + try { + const idParam = req.params.id; + if (!idParam) { + return res.status(400).json({ error: "Missing claim ID" }); + } + + const claimId = parseInt(idParam, 10); + if (isNaN(claimId)) { + return res.status(400).json({ error: "Invalid claim ID" }); + } + + const existingClaim = await storage.getClaim(claimId); + if (!existingClaim) { + return res.status(404).json({ message: "Claim not found" }); + } + + if (existingClaim.userId !== req.user!.id) { + return res.status(403).json({ message: "Forbidden" }); + } + + await storage.deleteClaim(claimId); + res.status(204).send(); + } catch (error) { + res.status(500).json({ message: "Failed to delete claim" }); + } +}); + +export default router; diff --git a/apps/Backend/src/routes/index.ts b/apps/Backend/src/routes/index.ts index f322ce1..3add2a6 100644 --- a/apps/Backend/src/routes/index.ts +++ b/apps/Backend/src/routes/index.ts @@ -4,6 +4,7 @@ import appointmentRoutes from './appointements' import userRoutes from './users' import staffRoutes from './staffs' import pdfExtractionRoutes from './pdfExtraction'; +import claimsRoutes from './claims'; const router = Router(); @@ -11,6 +12,7 @@ router.use('/patients', patientRoutes); router.use('/appointments', appointmentRoutes); router.use('/users', userRoutes); router.use('/staffs', staffRoutes); -router.use('/pdfExtraction/', pdfExtractionRoutes); +router.use('/pdfExtraction', pdfExtractionRoutes); +router.use('/claims', claimsRoutes); export default router; \ No newline at end of file diff --git a/apps/Backend/src/storage/index.ts b/apps/Backend/src/storage/index.ts index 6dad81b..3d41bac 100644 --- a/apps/Backend/src/storage/index.ts +++ b/apps/Backend/src/storage/index.ts @@ -4,6 +4,7 @@ import { PatientUncheckedCreateInputObjectSchema, UserUncheckedCreateInputObjectSchema, StaffUncheckedCreateInputObjectSchema, + ClaimUncheckedCreateInputObjectSchema, } from "@repo/db/usedSchemas"; import { z } from "zod"; @@ -94,6 +95,29 @@ type RegisterFormValues = z.infer; // staff types: type Staff = z.infer; +// Claim typse: +const insertClaimSchema = ( + ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + id: true, + createdAt: true, + updatedAt: true, +}); +type InsertClaim = z.infer; + +const updateClaimSchema = ( + ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject +) + .omit({ + id: true, + createdAt: true, + updatedAt: true, + }) + .partial(); +type UpdateClaim = z.infer; + +type Claim = z.infer; + export interface IStorage { // User methods getUser(id: number): Promise; @@ -127,6 +151,15 @@ export interface IStorage { createStaff(staff: Staff): Promise; updateStaff(id: number, updates: Partial): Promise; deleteStaff(id: number): Promise; + + // Claim methods + getClaim(id: number): Promise; + getClaimsByUserId(userId: number): Promise; + getClaimsByPatientId(patientId: number): Promise; + getClaimsByAppointmentId(appointmentId: number): Promise; + createClaim(claim: InsertClaim): Promise; + updateClaim(id: number, updates: UpdateClaim): Promise; + deleteClaim(id: number): Promise; } export const storage: IStorage = { @@ -281,4 +314,45 @@ export const storage: IStorage = { return false; } }, + + // Claim methods implementation + async getClaim(id: number): Promise { + const claim = await db.claim.findUnique({ where: { id } }); + return claim ?? undefined; + }, + + async getClaimsByUserId(userId: number): Promise { + return await db.claim.findMany({ where: { userId } }); + }, + + async getClaimsByPatientId(patientId: number): Promise { + return await db.claim.findMany({ where: { patientId } }); + }, + + async getClaimsByAppointmentId(appointmentId: number): Promise { + return await db.claim.findMany({ where: { appointmentId } }); + }, + + async createClaim(claim: InsertClaim): Promise { + return await db.claim.create({ data: claim as Claim }); + }, + + async updateClaim(id: number, updates: UpdateClaim): Promise { + try { + return await db.claim.update({ + where: { id }, + data: updates, + }); + } catch (err) { + throw new Error(`Claim with ID ${id} not found`); + } + }, + + async deleteClaim(id: number): Promise { + try { + await db.claim.delete({ where: { id } }); + } catch (err) { + throw new Error(`Claim with ID ${id} not found`); + } + }, }; diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index 8a86503..02f0a46 100644 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -35,6 +35,18 @@ const PatientSchema = ( }); type Patient = z.infer; +const updatePatientSchema = ( + PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject +) + .omit({ + id: true, + createdAt: true, + userId: true, + }) + .partial(); + +type UpdatePatient = z.infer; + //creating types out of schema auto generated. type Appointment = z.infer; @@ -73,6 +85,7 @@ interface ClaimFormProps { onHandleAppointmentSubmit: ( appointmentData: InsertAppointment | UpdateAppointment ) => void; + onHandleUpdatePatient: (patient: UpdatePatient & { id: number }) => void; onClose: () => void; } @@ -87,11 +100,13 @@ export function ClaimForm({ patientId, extractedData, onHandleAppointmentSubmit, + onHandleUpdatePatient, onSubmit, onClose, }: ClaimFormProps) { const { toast } = useToast(); + const [insuranceProvider, setInsuranceProvider] = useState(null) // Query patient if patientId provided const { data: fetchedPatient, @@ -254,6 +269,39 @@ export function ClaimForm({ setIsUploading(false); }; + const handleDeltaMASubmit = () => { + const appointmentData = { + patientId: patientId, + date: convertToISODate(serviceDate), + staffId: staff?.id, + }; + + // 1. Create or update appointment + onHandleAppointmentSubmit(appointmentData); + + // 2. Update patient + if (patient && typeof patient.id === "number") { + const { id, createdAt, userId, ...sanitizedFields } = patient; + const updatedPatientFields = { + id, + ... sanitizedFields, + insuranceProvider: "Delta MA", + } + onHandleUpdatePatient(updatedPatientFields); + } else { + toast({ + title: "Error", + description: "Cannot update patient: Missing or invalid patient data", + variant: "destructive", + }); + } + + // 3. Create Claim(if not) + + // 4. Close form + onClose(); + }; + return (
@@ -536,19 +584,7 @@ export function ClaimForm({ diff --git a/apps/Frontend/src/pages/claims-page.tsx b/apps/Frontend/src/pages/claims-page.tsx index 80634c9..24548b2 100644 --- a/apps/Frontend/src/pages/claims-page.tsx +++ b/apps/Frontend/src/pages/claims-page.tsx @@ -68,9 +68,6 @@ export default function ClaimsPage() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isClaimFormOpen, setIsClaimFormOpen] = useState(false); const [selectedPatient, setSelectedPatient] = useState(null); - const [selectedAppointment, setSelectedAppointment] = useState( - null - ); const { toast } = useToast(); const { user } = useAuth(); const [claimFormData, setClaimFormData] = useState({ @@ -126,6 +123,35 @@ export default function ClaimsPage() { }, }); + // Update patient mutation + const updatePatientMutation = useMutation({ + mutationFn: async ({ + id, + patient, + }: { + id: number; + patient: UpdatePatient; + }) => { + const res = await apiRequest("PUT", `/api/patients/${id}`, patient); + return res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/patients/"] }); + toast({ + title: "Success", + description: "Patient updated successfully!", + variant: "default", + }); + }, + onError: (error) => { + toast({ + title: "Error", + description: `Failed to update patient: ${error.message}`, + variant: "destructive", + }); + }, + }); + // Create appointment mutation const createAppointmentMutation = useMutation({ mutationFn: async (appointment: InsertAppointment) => { @@ -281,9 +307,8 @@ export default function ClaimsPage() { setIsMobileMenuOpen(!isMobileMenuOpen); }; - const handleNewClaim = (patientId: number, appointmentId: number) => { + const handleNewClaim = (patientId: number) => { setSelectedPatient(patientId); - setSelectedAppointment(appointmentId); setIsClaimFormOpen(true); const patient = patients.find((p) => p.id === patientId); @@ -295,7 +320,6 @@ export default function ClaimsPage() { const closeClaim = () => { setIsClaimFormOpen(false); setSelectedPatient(null); - setSelectedAppointment(null); setClaimFormData({ patientId: null, serviceDate: "", @@ -398,6 +422,24 @@ export default function ClaimsPage() { }> ); + // Update Patient ( for insuranceId and Insurance Provider) + const handleUpdatePatient = (patient: UpdatePatient & { id?: number }) => { + if (patient && user) { + const { id, ...sanitizedPatient } = patient; + updatePatientMutation.mutate({ + id: Number(patient.id), + patient: sanitizedPatient, + }); + } else { + console.error("No current patient or user found for update"); + toast({ + title: "Error", + description: "Cannot update patient: No patient or user found", + variant: "destructive", + }); + } + }; + return (
- handleNewClaim(item.patientId, item.appointmentId) + handleNewClaim(item.patientId) } >
@@ -519,6 +560,7 @@ export default function ClaimsPage() { extractedData={claimFormData} onSubmit={handleClaimSubmit} onHandleAppointmentSubmit={handleAppointmentSubmit} + onHandleUpdatePatient={handleUpdatePatient} /> )}
diff --git a/packages/db/usedSchemas/index.ts b/packages/db/usedSchemas/index.ts index d316829..6369e04 100644 --- a/packages/db/usedSchemas/index.ts +++ b/packages/db/usedSchemas/index.ts @@ -2,4 +2,5 @@ export * from '../shared/schemas/objects/AppointmentUncheckedCreateInput.schema'; export * from '../shared/schemas/objects/PatientUncheckedCreateInput.schema'; export * from '../shared/schemas/objects/UserUncheckedCreateInput.schema'; -export * from '../shared/schemas/objects/StaffUncheckedCreateInput.schema' \ No newline at end of file +export * from '../shared/schemas/objects/StaffUncheckedCreateInput.schema' +export * from '../shared/schemas/objects/ClaimUncheckedCreateInput.schema' \ No newline at end of file