diff --git a/apps/Backend/src/routes/appointments.ts b/apps/Backend/src/routes/appointments.ts index 3953238..b3d4ead 100644 --- a/apps/Backend/src/routes/appointments.ts +++ b/apps/Backend/src/routes/appointments.ts @@ -1,62 +1,14 @@ import { Router } from "express"; import type { Request, Response } from "express"; import { storage } from "../storage"; -import { - AppointmentUncheckedCreateInputObjectSchema, - PatientUncheckedCreateInputObjectSchema, -} from "@repo/db/usedSchemas"; import { z } from "zod"; +import { + insertAppointmentSchema, + updateAppointmentSchema, +} from "@repo/db/types"; const router = Router(); -//creating types out of schema auto generated. -type Appointment = z.infer; - -const insertAppointmentSchema = ( - AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ - id: true, - createdAt: true, -}); -type InsertAppointment = z.infer; - -const updateAppointmentSchema = ( - AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject -) - .omit({ - id: true, - createdAt: true, - }) - .partial(); -type UpdateAppointment = z.infer; - -const PatientSchema = ( - PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ - appointments: true, -}); -type Patient = z.infer; - -const insertPatientSchema = ( - PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ - id: true, - createdAt: true, -}); -type InsertPatient = z.infer; - -const updatePatientSchema = ( - PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject -) - .omit({ - id: true, - createdAt: true, - userId: true, - }) - .partial(); - -type UpdatePatient = z.infer; - // Get all appointments router.get("/all", async (req: Request, res: Response): Promise => { try { @@ -162,9 +114,7 @@ router.get("/appointments/recent", async (req: Request, res: Response) => { const offset = Math.max(0, parseInt(req.query.offset as string) || 0); const all = await storage.getRecentAppointments(limit, offset); - const filtered = all.filter((a) => a.userId === req.user!.id); - - res.json({ data: filtered, limit, offset }); + res.json({ data: all, limit, offset }); } catch (err) { res.status(500).json({ message: "Failed to get recent appointments" }); } diff --git a/apps/Backend/src/routes/claims.ts b/apps/Backend/src/routes/claims.ts index d354b63..5090259 100644 --- a/apps/Backend/src/routes/claims.ts +++ b/apps/Backend/src/routes/claims.ts @@ -166,8 +166,8 @@ router.get("/recent", async (req: Request, res: Response) => { const offset = parseInt(req.query.offset as string) || 0; const [claims, totalCount] = await Promise.all([ - storage.getRecentClaimsByUser(req.user!.id, limit, offset), - storage.getTotalClaimCountByUser(req.user!.id), + storage.getRecentClaims(limit, offset), + storage.getTotalClaimCount(), ]); res.json({ claims, totalCount }); @@ -210,13 +210,13 @@ router.get( } ); -// Get all claims for the logged-in user +// Get all claims count. router.get("/all", async (req: Request, res: Response) => { try { - const claims = await storage.getTotalClaimCountByUser(req.user!.id); + const claims = await storage.getTotalClaimCount(); res.json(claims); } catch (error) { - res.status(500).json({ message: "Failed to retrieve claims" }); + res.status(500).json({ message: "Failed to retrieve claims count" }); } }); diff --git a/apps/Backend/src/routes/insuranceCreds.ts b/apps/Backend/src/routes/insuranceCreds.ts index f2058da..1690be4 100644 --- a/apps/Backend/src/routes/insuranceCreds.ts +++ b/apps/Backend/src/routes/insuranceCreds.ts @@ -1,45 +1,50 @@ -import express, { Request, Response, NextFunction } from "express"; +import express, { Request, Response } from "express"; import { storage } from "../storage"; -import { InsuranceCredentialUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; import { z } from "zod"; +import { + insertInsuranceCredentialSchema, + InsuranceCredential, +} from "@repo/db/types"; const router = express.Router(); -// ✅ Types -type InsuranceCredential = z.infer; - -const insertInsuranceCredentialSchema = ( - InsuranceCredentialUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ id: true }); - -type InsertInsuranceCredential = z.infer; - // ✅ Get all credentials for a user -router.get("/", async (req: Request, res: Response):Promise => { +router.get("/", async (req: Request, res: Response): Promise => { try { if (!req.user || !req.user.id) { - return res.status(401).json({ message: "Unauthorized: user info missing" }); + return res + .status(401) + .json({ message: "Unauthorized: user info missing" }); } const userId = req.user.id; const credentials = await storage.getInsuranceCredentialsByUser(userId); return res.status(200).json(credentials); } catch (err) { - return res.status(500).json({ error: "Failed to fetch credentials", details: String(err) }); + return res + .status(500) + .json({ error: "Failed to fetch credentials", details: String(err) }); } }); // ✅ Create credential for a user -router.post("/", async (req: Request, res: Response):Promise => { +router.post("/", async (req: Request, res: Response): Promise => { try { if (!req.user || !req.user.id) { - return res.status(401).json({ message: "Unauthorized: user info missing" }); + return res + .status(401) + .json({ message: "Unauthorized: user info missing" }); } const userId = req.user.id; - const parseResult = insertInsuranceCredentialSchema.safeParse({ ...req.body, userId }); + const parseResult = insertInsuranceCredentialSchema.safeParse({ + ...req.body, + userId, + }); if (!parseResult.success) { - const flat = (parseResult as typeof parseResult & { error: z.ZodError }).error.flatten(); + const flat = ( + parseResult as typeof parseResult & { error: z.ZodError } + ).error.flatten(); const firstError = Object.values(flat.fieldErrors)[0]?.[0] || "Invalid input"; @@ -49,20 +54,24 @@ router.post("/", async (req: Request, res: Response):Promise => { }); } - const credential = await storage.createInsuranceCredential(parseResult.data); + const credential = await storage.createInsuranceCredential( + parseResult.data + ); return res.status(201).json(credential); - } catch (err:any) { + } catch (err: any) { if (err.code === "P2002") { - return res.status(400).json({ - message: `Credential with this ${err.meta?.target?.join(", ")} already exists.`, - }); - } - return res.status(500).json({ error: "Failed to create credential", details: String(err) }); + return res.status(400).json({ + message: `Credential with this ${err.meta?.target?.join(", ")} already exists.`, + }); + } + return res + .status(500) + .json({ error: "Failed to create credential", details: String(err) }); } }); // ✅ Update credential -router.put("/:id", async (req: Request, res: Response):Promise => { +router.put("/:id", async (req: Request, res: Response): Promise => { try { const id = Number(req.params.id); if (isNaN(id)) return res.status(400).send("Invalid credential ID"); @@ -71,20 +80,45 @@ router.put("/:id", async (req: Request, res: Response):Promise => { const credential = await storage.updateInsuranceCredential(id, updates); return res.status(200).json(credential); } catch (err) { - return res.status(500).json({ error: "Failed to update credential", details: String(err) }); + return res + .status(500) + .json({ error: "Failed to update credential", details: String(err) }); } }); // ✅ Delete a credential -router.delete("/:id", async (req: Request, res: Response):Promise => { +router.delete("/:id", async (req: Request, res: Response): Promise => { try { + const userId = (req as any).user?.id; + if (!userId) return res.status(401).json({ message: "Unauthorized" }); + const id = Number(req.params.id); if (isNaN(id)) return res.status(400).send("Invalid ID"); - await storage.deleteInsuranceCredential(id); + // 1) Check existence + const existing = await storage.getInsuranceCredential(userId); + if (!existing) + return res.status(404).json({ message: "Credential not found" }); + + // 2) Ownership check + if (existing.userId !== userId) { + return res + .status(403) + .json({ message: "Forbidden: Not your credential" }); + } + + // 3) Delete (storage method enforces userId + id) + const ok = await storage.deleteInsuranceCredential(userId, id); + if (!ok) { + return res + .status(404) + .json({ message: "Credential not found or already deleted" }); + } return res.status(204).send(); } catch (err) { - return res.status(500).json({ error: "Failed to delete credential", details: String(err) }); + return res + .status(500) + .json({ error: "Failed to delete credential", details: String(err) }); } }); diff --git a/apps/Backend/src/routes/notifications.ts b/apps/Backend/src/routes/notifications.ts index 72b583b..915e1d0 100644 --- a/apps/Backend/src/routes/notifications.ts +++ b/apps/Backend/src/routes/notifications.ts @@ -1,34 +1,69 @@ import { Router, Request, Response } from "express"; -import { prisma } from "@repo/db/client"; +import { storage } from "../storage"; const router = Router(); -router.get("/", async (req, res) => { - const userId = (req as any).user?.id; - const notifications = await prisma.notification.findMany({ - where: { userId }, - orderBy: { createdAt: "desc" }, - take: 20, - }); - res.json(notifications); +router.get("/", async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return res.status(401).json({ message: "Unauthorized" }); + + const notifications = await storage.getNotifications(userId, 20, 0); + res.json(notifications); + } catch (err) { + console.error("Failed to fetch notifications:", err); + res.status(500).json({ message: "Failed to fetch notifications" }); + } }); -router.post("/:id/read", async (req, res) => { - const userId = (req as any).user?.id; - await prisma.notification.updateMany({ - where: { id: Number(req.params.id), userId }, - data: { read: true }, - }); - res.json({ success: true }); +// Mark one notification as read +router.post("/:id/read", async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return res.status(401).json({ message: "Unauthorized" }); + + const success = await storage.markNotificationRead( + userId, + Number(req.params.id) + ); + + if (!success) + return res.status(404).json({ message: "Notification not found" }); + res.json({ success: true }); + } catch (err) { + console.error("Failed to mark notification as read:", err); + res.status(500).json({ message: "Failed to mark notification as read" }); + } }); -router.post("/read-all", async (req, res) => { - const userId = (req as any).user?.id; - await prisma.notification.updateMany({ - where: { userId }, - data: { read: true }, - }); - res.json({ success: true }); +// Mark all notifications as read +router.post("/read-all", async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return res.status(401).json({ message: "Unauthorized" }); + + const count = await storage.markAllNotificationsRead(userId); + res.json({ success: true, updatedCount: count }); + } catch (err) { + console.error("Failed to mark all notifications read:", err); + res.status(500).json({ message: "Failed to mark all notifications read" }); + } }); +router.delete( + "/delete-all", + async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return res.status(401).json({ message: "Unauthorized" }); + + const deletedCount = await storage.deleteAllNotifications(userId); + res.json({ success: true, deletedCount }); + } catch (err) { + console.error("Failed to delete notifications:", err); + res.status(500).json({ message: "Failed to delete notifications" }); + } + } +); + export default router; diff --git a/apps/Backend/src/routes/patients.ts b/apps/Backend/src/routes/patients.ts index f511652..bfc5be9 100644 --- a/apps/Backend/src/routes/patients.ts +++ b/apps/Backend/src/routes/patients.ts @@ -1,63 +1,11 @@ import { Router } from "express"; import type { Request, Response } from "express"; import { storage } from "../storage"; -import { - AppointmentUncheckedCreateInputObjectSchema, - PatientUncheckedCreateInputObjectSchema, -} from "@repo/db/usedSchemas"; import { z } from "zod"; -import { extractDobParts } from "../utils/DobParts"; +import { insertPatientSchema, updatePatientSchema } from "@repo/db/types"; const router = Router(); -//creating types out of schema auto generated. -type Appointment = z.infer; - -const insertAppointmentSchema = ( - AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ - id: true, - createdAt: true, -}); -type InsertAppointment = z.infer; - -const updateAppointmentSchema = ( - AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject -) - .omit({ - id: true, - createdAt: true, - }) - .partial(); -type UpdateAppointment = z.infer; - -const PatientSchema = ( - PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ - appointments: true, -}); -type Patient = z.infer; - -const insertPatientSchema = ( - PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject -).omit({ - id: true, - createdAt: true, -}); -type InsertPatient = z.infer; - -const updatePatientSchema = ( - PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject -) - .omit({ - id: true, - createdAt: true, - userId: true, - }) - .partial(); - -type UpdatePatient = z.infer; - // Patient Routes // Get all patients for the logged-in user router.get("/", async (req, res) => { @@ -100,9 +48,7 @@ router.get("/search", async (req: Request, res: Response): Promise => { offset = "0", } = req.query as Record; - const filters: any = { - userId: req.user!.id, - }; + const filters: any = {}; if (term) { filters.OR = [ @@ -159,28 +105,30 @@ router.get("/search", async (req: Request, res: Response): Promise => { } }); - // get patient by insurance id -router.get("/by-insurance-id", async (req: Request, res: Response): Promise => { - const insuranceId = req.query.insuranceId?.toString(); +router.get( + "/by-insurance-id", + async (req: Request, res: Response): Promise => { + const insuranceId = req.query.insuranceId?.toString(); - if (!insuranceId) { - return res.status(400).json({ error: "Missing insuranceId" }); - } - - try { - const patient = await storage.getPatientByInsuranceId(insuranceId); - - if (patient) { - return res.status(200).json(patient); - } else { - return res.status(200).json(null); + if (!insuranceId) { + return res.status(400).json({ error: "Missing insuranceId" }); + } + + try { + const patient = await storage.getPatientByInsuranceId(insuranceId); + + if (patient) { + return res.status(200).json(patient); + } else { + return res.status(200).json(null); + } + } catch (err) { + console.error("Failed to lookup patient:", err); + return res.status(500).json({ error: "Internal server error" }); } - } catch (err) { - console.error("Failed to lookup patient:", err); - return res.status(500).json({ error: "Internal server error" }); } -}); +); // Get a single patient by ID router.get( @@ -334,12 +282,12 @@ router.delete( } if (existingPatient.userId !== req.user!.id) { - console.warn( - `User ${req.user!.id} tried to delete patient ${patientId} owned by ${existingPatient.userId}` - ); return res .status(403) - .json({ message: "Forbidden: Patient belongs to a different user" }); + .json({ + message: + "Forbidden: Patient belongs to a different user, you can't delete this.", + }); } // Delete patient @@ -351,18 +299,6 @@ router.delete( } } ); - -// Appointment Routes -// Get all appointments for the logged-in user -router.get("/appointments", async (req, res) => { - try { - const appointments = await storage.getAppointmentsByUserId(req.user!.id); - res.json(appointments); - } catch (error) { - res.status(500).json({ message: "Failed to retrieve appointments" }); - } -}); - // Get appointments for a specific patient router.get( "/:patientId/appointments", diff --git a/apps/Backend/src/routes/payments.ts b/apps/Backend/src/routes/payments.ts index 3866cc1..6a56955 100644 --- a/apps/Backend/src/routes/payments.ts +++ b/apps/Backend/src/routes/payments.ts @@ -46,15 +46,12 @@ const router = Router(); // GET /api/payments/recent router.get("/recent", async (req: Request, res: Response): Promise => { try { - const userId = req.user?.id; - if (!userId) return res.status(401).json({ message: "Unauthorized" }); - const limit = parseInt(req.query.limit as string) || 10; const offset = parseInt(req.query.offset as string) || 0; const [payments, totalCount] = await Promise.all([ - storage.getRecentPaymentsByUser(userId, limit, offset), - storage.getTotalPaymentCountByUser(userId), + storage.getRecentPayments(limit, offset), + storage.getTotalPaymentCount(), ]); res.status(200).json({ payments, totalCount }); @@ -69,9 +66,6 @@ router.get( "/claim/:claimId", async (req: Request, res: Response): Promise => { try { - const userId = req.user?.id; - if (!userId) return res.status(401).json({ message: "Unauthorized" }); - const parsedClaimId = parseIntOrError(req.params.claimId, "Claim ID"); const payments = await storage.getPaymentsByClaimId(parsedClaimId); @@ -122,9 +116,6 @@ router.get( // GET /api/payments/filter router.get("/filter", async (req: Request, res: Response): Promise => { try { - const userId = req.user?.id; - if (!userId) return res.status(401).json({ message: "Unauthorized" }); - const validated = paymentFilterSchema.safeParse(req.query); if (!validated.success) { return res.status(400).json({ @@ -148,9 +139,6 @@ router.get("/filter", async (req: Request, res: Response): Promise => { // GET /api/payments/:id router.get("/:id", async (req: Request, res: Response): Promise => { try { - const userId = req.user?.id; - if (!userId) return res.status(401).json({ message: "Unauthorized" }); - const id = parseIntOrError(req.params.id, "Payment ID"); const payment = await storage.getPaymentById(id); @@ -351,6 +339,19 @@ router.delete("/:id", async (req: Request, res: Response): Promise => { if (!userId) return res.status(401).json({ message: "Unauthorized" }); const id = parseIntOrError(req.params.id, "Payment ID"); + + // Check if payment exists and belongs to this user + const existingPayment = await storage.getPayment(id); + if (!existingPayment) { + return res.status(404).json({ message: "Payment not found" }); + } + + if (existingPayment.userId !== req.user!.id) { + return res.status(403).json({ + message: + "Forbidden: Payment belongs to a different user, you can't delete this.", + }); + } await storage.deletePayment(id, userId); res.status(200).json({ message: "Payment deleted successfully" }); diff --git a/apps/Backend/src/routes/staffs.ts b/apps/Backend/src/routes/staffs.ts index 890674d..0660a4c 100644 --- a/apps/Backend/src/routes/staffs.ts +++ b/apps/Backend/src/routes/staffs.ts @@ -57,13 +57,43 @@ router.put("/:id", async (req: Request, res: Response): Promise => { } }); +const parseIdOr400 = (raw: any, label: string) => { + const n = Number(raw); + if (!Number.isFinite(n)) throw new Error(`${label} is invalid`); + return n; +}; + router.delete("/:id", async (req: Request, res: Response): Promise => { try { + const userId = req.user!.id; + const id = parseIdOr400(req.params.id, "Staff ID"); const parsedStaffId = Number(req.params.id); if (isNaN(parsedStaffId)) { return res.status(400).send("Invalid staff ID"); } + const existing = await storage.getStaff(id); // must include createdById + if (!existing) return res.status(404).json({ message: "Staff not found" }); + + if (existing.userId !== userId) { + return res.status(403).json({ + message: + "Forbidden: Staff was created by a different user; you cannot delete it.", + }); + } + + const [apptCount, claimCount] = await Promise.all([ + storage.countAppointmentsByStaffId(id), + storage.countClaimsByStaffId(id), + ]); + + if (apptCount || claimCount) { + return res.status(409).json({ + message: `Cannot delete staff with linked records. Appointment of this staff : ${apptCount} and Claims ${claimCount}`, + hint: "Archive this staff, or reassign linked records, then delete.", + }); + } + const deleted = await storage.deleteStaff(parsedStaffId); if (!deleted) return res.status(404).send("Staff not found"); diff --git a/apps/Backend/src/storage/index.ts b/apps/Backend/src/storage/index.ts index e37a507..1fbbf9f 100644 --- a/apps/Backend/src/storage/index.ts +++ b/apps/Backend/src/storage/index.ts @@ -107,6 +107,8 @@ export interface IStorage { createStaff(staff: Staff): Promise; updateStaff(id: number, updates: Partial): Promise; deleteStaff(id: number): Promise; + countAppointmentsByStaffId(staffId: number): Promise; + countClaimsByStaffId(staffId: number): Promise; // Claim methods getClaim(id: number): Promise; @@ -118,17 +120,14 @@ export interface IStorage { getTotalClaimCountByPatient(patientId: number): Promise; getClaimsByAppointmentId(appointmentId: number): Promise; - getRecentClaimsByUser( - userId: number, - limit: number, - offset: number - ): Promise; - getTotalClaimCountByUser(userId: number): Promise; + getRecentClaims(limit: number, offset: number): Promise; + getTotalClaimCount(): Promise; createClaim(claim: InsertClaim): Promise; updateClaim(id: number, updates: UpdateClaim): Promise; deleteClaim(id: number): Promise; // InsuranceCredential methods + getInsuranceCredential(id: number): Promise; getInsuranceCredentialsByUser(userId: number): Promise; createInsuranceCredential( data: InsertInsuranceCredential @@ -136,8 +135,8 @@ export interface IStorage { updateInsuranceCredential( id: number, updates: Partial - ): Promise; - deleteInsuranceCredential(id: number): Promise; + ): Promise; + deleteInsuranceCredential(userId: number, id: number): Promise; getInsuranceCredentialByUserAndSiteKey( userId: number, siteKey: string @@ -179,6 +178,7 @@ export interface IStorage { deletePdfGroup(id: number): Promise; // Payment methods: + getPayment(id: number): Promise; createPayment(data: InsertPayment): Promise; updatePayment(id: number, updates: UpdatePayment): Promise; deletePayment(id: number, userId: number): Promise; @@ -190,13 +190,12 @@ export interface IStorage { ): Promise; getTotalPaymentCountByPatient(patientId: number): Promise; getPaymentsByClaimId(claimId: number): Promise; - getRecentPaymentsByUser( - userId: number, + getRecentPayments( limit: number, offset: number ): Promise; getPaymentsByDateRange(from: Date, to: Date): Promise; - getTotalPaymentCountByUser(userId: number): Promise; + getTotalPaymentCount(): Promise; // Database Backup methods createBackup(userId: number): Promise; @@ -224,6 +223,7 @@ export interface IStorage { userId: number, type: NotificationTypes ): Promise; + deleteAllNotifications(userId: number): Promise; } export const storage: IStorage = { @@ -534,6 +534,14 @@ export const storage: IStorage = { } }, + async countAppointmentsByStaffId(staffId: number): Promise { + return await db.appointment.count({ where: { staffId } }); + }, + + async countClaimsByStaffId(staffId: number): Promise { + return await db.claim.count({ where: { staffId } }); + }, + // Claim methods implementation async getClaim(id: number): Promise { const claim = await db.claim.findUnique({ where: { id } }); @@ -567,13 +575,11 @@ export const storage: IStorage = { return await db.claim.findMany({ where: { appointmentId } }); }, - async getRecentClaimsByUser( - userId: number, + async getRecentClaims( limit: number, offset: number ): Promise { return db.claim.findMany({ - where: { userId }, orderBy: { createdAt: "desc" }, skip: offset, take: limit, @@ -581,8 +587,8 @@ export const storage: IStorage = { }); }, - async getTotalClaimCountByUser(userId: number): Promise { - return db.claim.count({ where: { userId } }); + async getTotalClaimCount(): Promise { + return db.claim.count(); }, async createClaim(claim: InsertClaim): Promise { @@ -609,6 +615,10 @@ export const storage: IStorage = { }, // Insurance Creds + async getInsuranceCredential(id: number) { + return await db.insuranceCredential.findUnique({ where: { id } }); + }, + async getInsuranceCredentialsByUser(userId: number) { return await db.insuranceCredential.findMany({ where: { userId } }); }, @@ -629,8 +639,13 @@ export const storage: IStorage = { }); }, - async deleteInsuranceCredential(id: number) { - await db.insuranceCredential.delete({ where: { id } }); + async deleteInsuranceCredential(userId: number, id: number) { + try { + await db.insuranceCredential.delete({ where: { userId, id } }); + return true; + } catch { + return false; + } }, async getInsuranceCredentialByUserAndSiteKey( @@ -759,6 +774,11 @@ export const storage: IStorage = { }, // Payment Methods + async getPayment(id: number): Promise { + const payment = await db.payment.findUnique({ where: { id } }); + return payment ?? undefined; + }, + async createPayment(payment: InsertPayment): Promise { return db.payment.create({ data: payment as Payment }); }, @@ -881,13 +901,11 @@ export const storage: IStorage = { }; }, - async getRecentPaymentsByUser( - userId: number, + async getRecentPayments( limit: number, offset: number ): Promise { const payments = await db.payment.findMany({ - where: { userId }, orderBy: { createdAt: "desc" }, skip: offset, take: limit, @@ -949,8 +967,8 @@ export const storage: IStorage = { })); }, - async getTotalPaymentCountByUser(userId: number): Promise { - return db.payment.count({ where: { userId } }); + async getTotalPaymentCount(): Promise { + return db.payment.count(); }, // ============================== @@ -1024,4 +1042,11 @@ export const storage: IStorage = { }); return result.count; }, + + async deleteAllNotifications(userId: number): Promise { + const result = await db.notification.deleteMany({ + where: { userId }, + }); + return result.count; + }, }; diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index 862b25e..2ca5049 100644 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -44,6 +44,7 @@ import { applyComboToForm, getDescriptionForCode, } from "@/utils/procedureCombosMapping"; +import { COMBO_CATEGORIES, PROCEDURE_COMBOS } from "@/utils/procedureCombos"; interface ClaimFormData { patientId: number; @@ -707,32 +708,49 @@ export function ClaimForm({ + Add Service Line -
- {COMBO_BUTTONS.map((b) => ( - +
+ {Object.entries(COMBO_CATEGORIES).map(([section, ids]) => ( +
+
+ {section} +
+
+ {ids.map((id) => { + const b = PROCEDURE_COMBOS[id]; + if(!b){return} + return ( + + ); + })} +
+
))} - + +
+ +
+
{/* File Upload Section */} diff --git a/apps/Frontend/src/utils/procedureCombos.ts b/apps/Frontend/src/utils/procedureCombos.ts index 7391b51..0082b78 100644 --- a/apps/Frontend/src/utils/procedureCombos.ts +++ b/apps/Frontend/src/utils/procedureCombos.ts @@ -10,28 +10,186 @@ export const PROCEDURE_COMBOS: Record< "D1120", "D0272", "D1208", - "D2331", - "D0120", - "D1120", - "D0272", - "D1208", - "D2331", - "D0120", - "D1120", - "D0272", - "D1208", - "D2331", ], }, - adultProphy: { - id: "adultProphy", - label: "Adult Prophy", - codes: ["D0150", "D1110", "D0274", "D1208"], + adultRecall: { + id: "adultRecall", + label: "Adult Recall", + codes: ["D0120", "D0220", "D0230", "D0274", "D1110"], }, - bitewingsOnly: { - id: "bitewingsOnly", - label: "Bitewings Only", - codes: ["D0272"], + newChildPatient: { + id: "newChildPatient", + label: "New Child Patient", + codes: ["D0150", "D1120", "D1208"], + }, + newAdultPatientPano: { + id: "newAdultPatientPano", + label: "New Adult Patient (Pano)", + codes: ["D0150", "D0330", "D1110"], + }, + newAdultPatientFMX: { + id: "newAdultPatientFMX", + label: "New Adult Patient (FMX)", + codes: ["D0150", "D0210", "D1110"], + }, + + //Compostie + oneSurfCompFront: { + id: "oneSurfCompFront", + label: "One Surface Composite (Front)", + codes: ["D2330"], + }, + oneSurfCompBack: { + id: "oneSurfCompBack", + label: "One Surface Composite (Back)", + codes: ["D2391"], + }, + twoSurfCompFront: { + id: "twoSurfCompFront", + label: "Two Surface Composite (Front)", + codes: ["D2331"], + }, + twoSurfCompBack: { + id: "twoSurfCompBack", + label: "Two Surface Composite (Back)", + codes: ["D2392"], + }, + threeSurfCompFront: { + id: "threeSurfCompFront", + label: "Three Surface Composite (Front)", + codes: ["D2332"], + }, + threeSurfCompBack: { + id: "threeSurfCompBack", + label: "Three Surface Composite (Back)", + codes: ["D2393"], + }, + fourSurfCompFront: { + id: "fourSurfCompFront", + label: "Four Surface Composite (Front)", + codes: ["D2335"], + }, + fourSurfCompBack: { + id: "fourSurfCompBack", + label: "Four Surface Composite (Back)", + codes: ["D2394"], + }, + + // Dentures / Partials + fu: { + id: "fu", + label: "FU", + codes: ["D5110"], + }, + fl: { + id: "fl", + label: "FL", + codes: ["D5120"], + }, + puResin: { + id: "puResin", + label: "PU (Resin)", + codes: ["D5211"], + }, + puCast: { + id: "puCast", + label: "PU (Cast)", + codes: ["D5213"], + }, + plResin: { + id: "plResin", + label: "PL (Resin)", + codes: ["D5212"], + }, + plCast: { + id: "plCast", + label: "PL (Cast)", + codes: ["D5214"], + }, + + // Endodontics + rctAnterior: { + id: "rctAnterior", + label: "RCT Anterior", + codes: ["D3310"], + }, + rctPremolar: { + id: "rctPremolar", + label: "RCT PreM", + codes: ["D3320"], + }, + rctMolar: { + id: "rctMolar", + label: "RCT Molar", + codes: ["D3330"], + }, + postCore: { + id: "postCore", + label: "Post/Core", + codes: ["D2954"], + }, + + // Prostho / Perio / Oral Surgery + crown: { + id: "crown", + label: "Crown", + codes: ["D2740"], + }, + deepCleaning: { + id: "deepCleaning", + label: "Deep Cleaning", + codes: ["D4341"], + }, + simpleExtraction: { + id: "simpleExtraction", + label: "Simple EXT", + codes: ["D7140"], + }, + surgicalExtraction: { + id: "surgicalExtraction", + label: "Surg EXT", + codes: ["D7210"], + }, + babyTeethExtraction: { + id: "babyTeethExtraction", + label: "Baby Teeth EXT", + codes: ["D7111"], }, // add more… }; + + +// Which combos appear under which heading +export const COMBO_CATEGORIES: Record = { + "Recalls & New Patients": [ + "childRecall", + "adultRecall", + "newAdultPatientPano", + "newAdultPatientFMX", + "newChildPatient", + ], + "Composite Fillings (Front)": [ + "oneSurfCompFront", + "twoSurfCompFront", + "threeSurfCompFront", + "fourSurfCompFront", + ], + "Composite Fillings (Back)": [ + "oneSurfCompBack", + "twoSurfCompBack", + "threeSurfCompBack", + "fourSurfCompBack", + ], + "Dentures / Partials (>21 price)": [ + "fu", + "fl", + "puResin", + "puCast", + "plResin", + "plCast", + ], + Endodontics: ["rctAnterior", "rctPremolar", "rctMolar", "postCore"], + Prosthodontics: ["crown"], + Periodontics: ["deepCleaning"], + Extractions: ["simpleExtraction", "surgicalExtraction", "babyTeethExtraction"], +}; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 6a058c3..aa08cd4 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -25,6 +25,7 @@ model User { password String patients Patient[] appointments Appointment[] + staff Staff[] claims Claim[] insuranceCredentials InsuranceCredential[] updatedPayments Payment[] @relation("PaymentUpdatedBy") @@ -87,11 +88,13 @@ model Appointment { model Staff { id Int @id @default(autoincrement()) + userId Int? name String email String? role String // e.g., "Dentist", "Hygienist", "Assistant" phone String? createdAt DateTime @default(now()) + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) appointments Appointment[] claims Claim[] @relation("ClaimStaff") } diff --git a/readmeForMigration.txt b/readmeForMigration.txt new file mode 100644 index 0000000..d3466e2 --- /dev/null +++ b/readmeForMigration.txt @@ -0,0 +1,5 @@ +for staff model migration: + +after npm run db migrate, + +either by sql or manually add userid to each row of staff model. \ No newline at end of file