routes fixed - user based fixed done

This commit is contained in:
2025-08-28 22:28:41 +05:30
parent 269cdf29b3
commit 848e4362e5
12 changed files with 478 additions and 283 deletions

View File

@@ -1,62 +1,14 @@
import { Router } from "express"; import { Router } from "express";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { storage } from "../storage"; import { storage } from "../storage";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { z } from "zod"; import { z } from "zod";
import {
insertAppointmentSchema,
updateAppointmentSchema,
} from "@repo/db/types";
const router = Router(); const router = Router();
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
})
.partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Get all appointments // Get all appointments
router.get("/all", async (req: Request, res: Response): Promise<any> => { router.get("/all", async (req: Request, res: Response): Promise<any> => {
try { 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 offset = Math.max(0, parseInt(req.query.offset as string) || 0);
const all = await storage.getRecentAppointments(limit, offset); const all = await storage.getRecentAppointments(limit, offset);
const filtered = all.filter((a) => a.userId === req.user!.id); res.json({ data: all, limit, offset });
res.json({ data: filtered, limit, offset });
} catch (err) { } catch (err) {
res.status(500).json({ message: "Failed to get recent appointments" }); res.status(500).json({ message: "Failed to get recent appointments" });
} }

View File

@@ -166,8 +166,8 @@ router.get("/recent", async (req: Request, res: Response) => {
const offset = parseInt(req.query.offset as string) || 0; const offset = parseInt(req.query.offset as string) || 0;
const [claims, totalCount] = await Promise.all([ const [claims, totalCount] = await Promise.all([
storage.getRecentClaimsByUser(req.user!.id, limit, offset), storage.getRecentClaims(limit, offset),
storage.getTotalClaimCountByUser(req.user!.id), storage.getTotalClaimCount(),
]); ]);
res.json({ claims, totalCount }); 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) => { router.get("/all", async (req: Request, res: Response) => {
try { try {
const claims = await storage.getTotalClaimCountByUser(req.user!.id); const claims = await storage.getTotalClaimCount();
res.json(claims); res.json(claims);
} catch (error) { } catch (error) {
res.status(500).json({ message: "Failed to retrieve claims" }); res.status(500).json({ message: "Failed to retrieve claims count" });
} }
}); });

View File

@@ -1,45 +1,50 @@
import express, { Request, Response, NextFunction } from "express"; import express, { Request, Response } from "express";
import { storage } from "../storage"; import { storage } from "../storage";
import { InsuranceCredentialUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { z } from "zod"; import { z } from "zod";
import {
insertInsuranceCredentialSchema,
InsuranceCredential,
} from "@repo/db/types";
const router = express.Router(); const router = express.Router();
// ✅ Types
type InsuranceCredential = z.infer<typeof InsuranceCredentialUncheckedCreateInputObjectSchema>;
const insertInsuranceCredentialSchema = (
InsuranceCredentialUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({ id: true });
type InsertInsuranceCredential = z.infer<typeof insertInsuranceCredentialSchema>;
// ✅ Get all credentials for a user // ✅ Get all credentials for a user
router.get("/", async (req: Request, res: Response):Promise<any> => { router.get("/", async (req: Request, res: Response): Promise<any> => {
try { try {
if (!req.user || !req.user.id) { 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 userId = req.user.id;
const credentials = await storage.getInsuranceCredentialsByUser(userId); const credentials = await storage.getInsuranceCredentialsByUser(userId);
return res.status(200).json(credentials); return res.status(200).json(credentials);
} catch (err) { } 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 // ✅ Create credential for a user
router.post("/", async (req: Request, res: Response):Promise<any> => { router.post("/", async (req: Request, res: Response): Promise<any> => {
try { try {
if (!req.user || !req.user.id) { 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 userId = req.user.id;
const parseResult = insertInsuranceCredentialSchema.safeParse({ ...req.body, userId }); const parseResult = insertInsuranceCredentialSchema.safeParse({
...req.body,
userId,
});
if (!parseResult.success) { if (!parseResult.success) {
const flat = (parseResult as typeof parseResult & { error: z.ZodError<any> }).error.flatten(); const flat = (
parseResult as typeof parseResult & { error: z.ZodError<any> }
).error.flatten();
const firstError = const firstError =
Object.values(flat.fieldErrors)[0]?.[0] || "Invalid input"; Object.values(flat.fieldErrors)[0]?.[0] || "Invalid input";
@@ -49,20 +54,24 @@ router.post("/", async (req: Request, res: Response):Promise<any> => {
}); });
} }
const credential = await storage.createInsuranceCredential(parseResult.data); const credential = await storage.createInsuranceCredential(
parseResult.data
);
return res.status(201).json(credential); return res.status(201).json(credential);
} catch (err:any) { } catch (err: any) {
if (err.code === "P2002") { if (err.code === "P2002") {
return res.status(400).json({ return res.status(400).json({
message: `Credential with this ${err.meta?.target?.join(", ")} already exists.`, 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(500)
.json({ error: "Failed to create credential", details: String(err) });
} }
}); });
// ✅ Update credential // ✅ Update credential
router.put("/:id", async (req: Request, res: Response):Promise<any> => { router.put("/:id", async (req: Request, res: Response): Promise<any> => {
try { try {
const id = Number(req.params.id); const id = Number(req.params.id);
if (isNaN(id)) return res.status(400).send("Invalid credential 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<any> => {
const credential = await storage.updateInsuranceCredential(id, updates); const credential = await storage.updateInsuranceCredential(id, updates);
return res.status(200).json(credential); return res.status(200).json(credential);
} catch (err) { } 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 // ✅ Delete a credential
router.delete("/:id", async (req: Request, res: Response):Promise<any> => { router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
try { try {
const userId = (req as any).user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const id = Number(req.params.id); const id = Number(req.params.id);
if (isNaN(id)) return res.status(400).send("Invalid 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(); return res.status(204).send();
} catch (err) { } 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) });
} }
}); });

View File

@@ -1,34 +1,69 @@
import { Router, Request, Response } from "express"; import { Router, Request, Response } from "express";
import { prisma } from "@repo/db/client"; import { storage } from "../storage";
const router = Router(); const router = Router();
router.get("/", async (req, res) => { router.get("/", async (req: Request, res: Response): Promise<any> => {
const userId = (req as any).user?.id; try {
const notifications = await prisma.notification.findMany({ const userId = (req as any).user?.id;
where: { userId }, if (!userId) return res.status(401).json({ message: "Unauthorized" });
orderBy: { createdAt: "desc" },
take: 20, const notifications = await storage.getNotifications(userId, 20, 0);
}); res.json(notifications);
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) => { // Mark one notification as read
const userId = (req as any).user?.id; router.post("/:id/read", async (req: Request, res: Response): Promise<any> => {
await prisma.notification.updateMany({ try {
where: { id: Number(req.params.id), userId }, const userId = (req as any).user?.id;
data: { read: true }, if (!userId) return res.status(401).json({ message: "Unauthorized" });
});
res.json({ success: true }); 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) => { // Mark all notifications as read
const userId = (req as any).user?.id; router.post("/read-all", async (req: Request, res: Response): Promise<any> => {
await prisma.notification.updateMany({ try {
where: { userId }, const userId = (req as any).user?.id;
data: { read: true }, if (!userId) return res.status(401).json({ message: "Unauthorized" });
});
res.json({ success: true }); 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<any> => {
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; export default router;

View File

@@ -1,63 +1,11 @@
import { Router } from "express"; import { Router } from "express";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { storage } from "../storage"; import { storage } from "../storage";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { z } from "zod"; import { z } from "zod";
import { extractDobParts } from "../utils/DobParts"; import { insertPatientSchema, updatePatientSchema } from "@repo/db/types";
const router = Router(); const router = Router();
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
})
.partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
// Patient Routes // Patient Routes
// Get all patients for the logged-in user // Get all patients for the logged-in user
router.get("/", async (req, res) => { router.get("/", async (req, res) => {
@@ -100,9 +48,7 @@ router.get("/search", async (req: Request, res: Response): Promise<any> => {
offset = "0", offset = "0",
} = req.query as Record<string, string>; } = req.query as Record<string, string>;
const filters: any = { const filters: any = {};
userId: req.user!.id,
};
if (term) { if (term) {
filters.OR = [ filters.OR = [
@@ -159,28 +105,30 @@ router.get("/search", async (req: Request, res: Response): Promise<any> => {
} }
}); });
// get patient by insurance id // get patient by insurance id
router.get("/by-insurance-id", async (req: Request, res: Response): Promise<any> => { router.get(
const insuranceId = req.query.insuranceId?.toString(); "/by-insurance-id",
async (req: Request, res: Response): Promise<any> => {
const insuranceId = req.query.insuranceId?.toString();
if (!insuranceId) { if (!insuranceId) {
return res.status(400).json({ error: "Missing insuranceId" }); return res.status(400).json({ error: "Missing insuranceId" });
} }
try { try {
const patient = await storage.getPatientByInsuranceId(insuranceId); const patient = await storage.getPatientByInsuranceId(insuranceId);
if (patient) { if (patient) {
return res.status(200).json(patient); return res.status(200).json(patient);
} else { } else {
return res.status(200).json(null); 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 // Get a single patient by ID
router.get( router.get(
@@ -334,12 +282,12 @@ router.delete(
} }
if (existingPatient.userId !== req.user!.id) { if (existingPatient.userId !== req.user!.id) {
console.warn(
`User ${req.user!.id} tried to delete patient ${patientId} owned by ${existingPatient.userId}`
);
return res return res
.status(403) .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 // 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 // Get appointments for a specific patient
router.get( router.get(
"/:patientId/appointments", "/:patientId/appointments",

View File

@@ -46,15 +46,12 @@ const router = Router();
// GET /api/payments/recent // GET /api/payments/recent
router.get("/recent", async (req: Request, res: Response): Promise<any> => { router.get("/recent", async (req: Request, res: Response): Promise<any> => {
try { 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 limit = parseInt(req.query.limit as string) || 10;
const offset = parseInt(req.query.offset as string) || 0; const offset = parseInt(req.query.offset as string) || 0;
const [payments, totalCount] = await Promise.all([ const [payments, totalCount] = await Promise.all([
storage.getRecentPaymentsByUser(userId, limit, offset), storage.getRecentPayments(limit, offset),
storage.getTotalPaymentCountByUser(userId), storage.getTotalPaymentCount(),
]); ]);
res.status(200).json({ payments, totalCount }); res.status(200).json({ payments, totalCount });
@@ -69,9 +66,6 @@ router.get(
"/claim/:claimId", "/claim/:claimId",
async (req: Request, res: Response): Promise<any> => { async (req: Request, res: Response): Promise<any> => {
try { try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const parsedClaimId = parseIntOrError(req.params.claimId, "Claim ID"); const parsedClaimId = parseIntOrError(req.params.claimId, "Claim ID");
const payments = await storage.getPaymentsByClaimId(parsedClaimId); const payments = await storage.getPaymentsByClaimId(parsedClaimId);
@@ -122,9 +116,6 @@ router.get(
// GET /api/payments/filter // GET /api/payments/filter
router.get("/filter", async (req: Request, res: Response): Promise<any> => { router.get("/filter", async (req: Request, res: Response): Promise<any> => {
try { try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const validated = paymentFilterSchema.safeParse(req.query); const validated = paymentFilterSchema.safeParse(req.query);
if (!validated.success) { if (!validated.success) {
return res.status(400).json({ return res.status(400).json({
@@ -148,9 +139,6 @@ router.get("/filter", async (req: Request, res: Response): Promise<any> => {
// GET /api/payments/:id // GET /api/payments/:id
router.get("/:id", async (req: Request, res: Response): Promise<any> => { router.get("/:id", async (req: Request, res: Response): Promise<any> => {
try { try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const id = parseIntOrError(req.params.id, "Payment ID"); const id = parseIntOrError(req.params.id, "Payment ID");
const payment = await storage.getPaymentById(id); const payment = await storage.getPaymentById(id);
@@ -351,6 +339,19 @@ router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
if (!userId) return res.status(401).json({ message: "Unauthorized" }); if (!userId) return res.status(401).json({ message: "Unauthorized" });
const id = parseIntOrError(req.params.id, "Payment ID"); 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); await storage.deletePayment(id, userId);
res.status(200).json({ message: "Payment deleted successfully" }); res.status(200).json({ message: "Payment deleted successfully" });

View File

@@ -57,13 +57,43 @@ router.put("/:id", async (req: Request, res: Response): Promise<any> => {
} }
}); });
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<any> => { router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
try { try {
const userId = req.user!.id;
const id = parseIdOr400(req.params.id, "Staff ID");
const parsedStaffId = Number(req.params.id); const parsedStaffId = Number(req.params.id);
if (isNaN(parsedStaffId)) { if (isNaN(parsedStaffId)) {
return res.status(400).send("Invalid staff ID"); 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); const deleted = await storage.deleteStaff(parsedStaffId);
if (!deleted) return res.status(404).send("Staff not found"); if (!deleted) return res.status(404).send("Staff not found");

View File

@@ -107,6 +107,8 @@ export interface IStorage {
createStaff(staff: Staff): Promise<Staff>; createStaff(staff: Staff): Promise<Staff>;
updateStaff(id: number, updates: Partial<Staff>): Promise<Staff | undefined>; updateStaff(id: number, updates: Partial<Staff>): Promise<Staff | undefined>;
deleteStaff(id: number): Promise<boolean>; deleteStaff(id: number): Promise<boolean>;
countAppointmentsByStaffId(staffId: number): Promise<number>;
countClaimsByStaffId(staffId: number): Promise<number>;
// Claim methods // Claim methods
getClaim(id: number): Promise<Claim | undefined>; getClaim(id: number): Promise<Claim | undefined>;
@@ -118,17 +120,14 @@ export interface IStorage {
getTotalClaimCountByPatient(patientId: number): Promise<number>; getTotalClaimCountByPatient(patientId: number): Promise<number>;
getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]>; getClaimsByAppointmentId(appointmentId: number): Promise<Claim[]>;
getRecentClaimsByUser( getRecentClaims(limit: number, offset: number): Promise<Claim[]>;
userId: number, getTotalClaimCount(): Promise<number>;
limit: number,
offset: number
): Promise<Claim[]>;
getTotalClaimCountByUser(userId: number): Promise<number>;
createClaim(claim: InsertClaim): Promise<Claim>; createClaim(claim: InsertClaim): Promise<Claim>;
updateClaim(id: number, updates: UpdateClaim): Promise<Claim>; updateClaim(id: number, updates: UpdateClaim): Promise<Claim>;
deleteClaim(id: number): Promise<void>; deleteClaim(id: number): Promise<void>;
// InsuranceCredential methods // InsuranceCredential methods
getInsuranceCredential(id: number): Promise<InsuranceCredential | null>;
getInsuranceCredentialsByUser(userId: number): Promise<InsuranceCredential[]>; getInsuranceCredentialsByUser(userId: number): Promise<InsuranceCredential[]>;
createInsuranceCredential( createInsuranceCredential(
data: InsertInsuranceCredential data: InsertInsuranceCredential
@@ -136,8 +135,8 @@ export interface IStorage {
updateInsuranceCredential( updateInsuranceCredential(
id: number, id: number,
updates: Partial<InsuranceCredential> updates: Partial<InsuranceCredential>
): Promise<InsuranceCredential>; ): Promise<InsuranceCredential | null>;
deleteInsuranceCredential(id: number): Promise<void>; deleteInsuranceCredential(userId: number, id: number): Promise<boolean>;
getInsuranceCredentialByUserAndSiteKey( getInsuranceCredentialByUserAndSiteKey(
userId: number, userId: number,
siteKey: string siteKey: string
@@ -179,6 +178,7 @@ export interface IStorage {
deletePdfGroup(id: number): Promise<boolean>; deletePdfGroup(id: number): Promise<boolean>;
// Payment methods: // Payment methods:
getPayment(id: number): Promise<Payment | undefined>;
createPayment(data: InsertPayment): Promise<Payment>; createPayment(data: InsertPayment): Promise<Payment>;
updatePayment(id: number, updates: UpdatePayment): Promise<Payment>; updatePayment(id: number, updates: UpdatePayment): Promise<Payment>;
deletePayment(id: number, userId: number): Promise<void>; deletePayment(id: number, userId: number): Promise<void>;
@@ -190,13 +190,12 @@ export interface IStorage {
): Promise<PaymentWithExtras[] | null>; ): Promise<PaymentWithExtras[] | null>;
getTotalPaymentCountByPatient(patientId: number): Promise<number>; getTotalPaymentCountByPatient(patientId: number): Promise<number>;
getPaymentsByClaimId(claimId: number): Promise<PaymentWithExtras | null>; getPaymentsByClaimId(claimId: number): Promise<PaymentWithExtras | null>;
getRecentPaymentsByUser( getRecentPayments(
userId: number,
limit: number, limit: number,
offset: number offset: number
): Promise<PaymentWithExtras[]>; ): Promise<PaymentWithExtras[]>;
getPaymentsByDateRange(from: Date, to: Date): Promise<PaymentWithExtras[]>; getPaymentsByDateRange(from: Date, to: Date): Promise<PaymentWithExtras[]>;
getTotalPaymentCountByUser(userId: number): Promise<number>; getTotalPaymentCount(): Promise<number>;
// Database Backup methods // Database Backup methods
createBackup(userId: number): Promise<DatabaseBackup>; createBackup(userId: number): Promise<DatabaseBackup>;
@@ -224,6 +223,7 @@ export interface IStorage {
userId: number, userId: number,
type: NotificationTypes type: NotificationTypes
): Promise<number>; ): Promise<number>;
deleteAllNotifications(userId: number): Promise<number>;
} }
export const storage: IStorage = { export const storage: IStorage = {
@@ -534,6 +534,14 @@ export const storage: IStorage = {
} }
}, },
async countAppointmentsByStaffId(staffId: number): Promise<number> {
return await db.appointment.count({ where: { staffId } });
},
async countClaimsByStaffId(staffId: number): Promise<number> {
return await db.claim.count({ where: { staffId } });
},
// Claim methods implementation // Claim methods implementation
async getClaim(id: number): Promise<Claim | undefined> { async getClaim(id: number): Promise<Claim | undefined> {
const claim = await db.claim.findUnique({ where: { id } }); const claim = await db.claim.findUnique({ where: { id } });
@@ -567,13 +575,11 @@ export const storage: IStorage = {
return await db.claim.findMany({ where: { appointmentId } }); return await db.claim.findMany({ where: { appointmentId } });
}, },
async getRecentClaimsByUser( async getRecentClaims(
userId: number,
limit: number, limit: number,
offset: number offset: number
): Promise<ClaimWithServiceLines[]> { ): Promise<ClaimWithServiceLines[]> {
return db.claim.findMany({ return db.claim.findMany({
where: { userId },
orderBy: { createdAt: "desc" }, orderBy: { createdAt: "desc" },
skip: offset, skip: offset,
take: limit, take: limit,
@@ -581,8 +587,8 @@ export const storage: IStorage = {
}); });
}, },
async getTotalClaimCountByUser(userId: number): Promise<number> { async getTotalClaimCount(): Promise<number> {
return db.claim.count({ where: { userId } }); return db.claim.count();
}, },
async createClaim(claim: InsertClaim): Promise<Claim> { async createClaim(claim: InsertClaim): Promise<Claim> {
@@ -609,6 +615,10 @@ export const storage: IStorage = {
}, },
// Insurance Creds // Insurance Creds
async getInsuranceCredential(id: number) {
return await db.insuranceCredential.findUnique({ where: { id } });
},
async getInsuranceCredentialsByUser(userId: number) { async getInsuranceCredentialsByUser(userId: number) {
return await db.insuranceCredential.findMany({ where: { userId } }); return await db.insuranceCredential.findMany({ where: { userId } });
}, },
@@ -629,8 +639,13 @@ export const storage: IStorage = {
}); });
}, },
async deleteInsuranceCredential(id: number) { async deleteInsuranceCredential(userId: number, id: number) {
await db.insuranceCredential.delete({ where: { id } }); try {
await db.insuranceCredential.delete({ where: { userId, id } });
return true;
} catch {
return false;
}
}, },
async getInsuranceCredentialByUserAndSiteKey( async getInsuranceCredentialByUserAndSiteKey(
@@ -759,6 +774,11 @@ export const storage: IStorage = {
}, },
// Payment Methods // Payment Methods
async getPayment(id: number): Promise<Payment | undefined> {
const payment = await db.payment.findUnique({ where: { id } });
return payment ?? undefined;
},
async createPayment(payment: InsertPayment): Promise<Payment> { async createPayment(payment: InsertPayment): Promise<Payment> {
return db.payment.create({ data: payment as Payment }); return db.payment.create({ data: payment as Payment });
}, },
@@ -881,13 +901,11 @@ export const storage: IStorage = {
}; };
}, },
async getRecentPaymentsByUser( async getRecentPayments(
userId: number,
limit: number, limit: number,
offset: number offset: number
): Promise<PaymentWithExtras[]> { ): Promise<PaymentWithExtras[]> {
const payments = await db.payment.findMany({ const payments = await db.payment.findMany({
where: { userId },
orderBy: { createdAt: "desc" }, orderBy: { createdAt: "desc" },
skip: offset, skip: offset,
take: limit, take: limit,
@@ -949,8 +967,8 @@ export const storage: IStorage = {
})); }));
}, },
async getTotalPaymentCountByUser(userId: number): Promise<number> { async getTotalPaymentCount(): Promise<number> {
return db.payment.count({ where: { userId } }); return db.payment.count();
}, },
// ============================== // ==============================
@@ -1024,4 +1042,11 @@ export const storage: IStorage = {
}); });
return result.count; return result.count;
}, },
async deleteAllNotifications(userId: number): Promise<number> {
const result = await db.notification.deleteMany({
where: { userId },
});
return result.count;
},
}; };

View File

@@ -44,6 +44,7 @@ import {
applyComboToForm, applyComboToForm,
getDescriptionForCode, getDescriptionForCode,
} from "@/utils/procedureCombosMapping"; } from "@/utils/procedureCombosMapping";
import { COMBO_CATEGORIES, PROCEDURE_COMBOS } from "@/utils/procedureCombos";
interface ClaimFormData { interface ClaimFormData {
patientId: number; patientId: number;
@@ -707,32 +708,49 @@ export function ClaimForm({
+ Add Service Line + Add Service Line
</Button> </Button>
<div className="flex gap-2 mt-10 mb-10"> <div className="space-y-8 mt-10 mb-10">
{COMBO_BUTTONS.map((b) => ( {Object.entries(COMBO_CATEGORIES).map(([section, ids]) => (
<Button <div key={section}>
key={b.id} <div className="mb-3 text-sm font-semibold opacity-70">
variant="secondary" {section}
onClick={() => </div>
setForm((prev) => <div className="flex flex-wrap gap-2">
applyComboToForm( {ids.map((id) => {
prev, const b = PROCEDURE_COMBOS[id];
b.id as any, if(!b){return}
patient?.dateOfBirth ?? "", return (
{ <Button
replaceAll: true, key={b.id}
lineDate: prev.serviceDate, variant="secondary"
} onClick={() =>
) setForm((prev) =>
) applyComboToForm(
} prev,
> b.id as any,
{b.label} patient?.dateOfBirth ?? "",
</Button> {
replaceAll: true,
lineDate: prev.serviceDate,
}
)
)
}
>
{b.label}
</Button>
);
})}
</div>
</div>
))} ))}
<Button variant="success" onClick={onMapPrice}>
Map Price <div className="pt-4">
</Button> <Button variant="success" onClick={onMapPrice}>
Map Price
</Button>
</div>
</div> </div>
</div> </div>
{/* File Upload Section */} {/* File Upload Section */}

View File

@@ -10,28 +10,186 @@ export const PROCEDURE_COMBOS: Record<
"D1120", "D1120",
"D0272", "D0272",
"D1208", "D1208",
"D2331",
"D0120",
"D1120",
"D0272",
"D1208",
"D2331",
"D0120",
"D1120",
"D0272",
"D1208",
"D2331",
], ],
}, },
adultProphy: { adultRecall: {
id: "adultProphy", id: "adultRecall",
label: "Adult Prophy", label: "Adult Recall",
codes: ["D0150", "D1110", "D0274", "D1208"], codes: ["D0120", "D0220", "D0230", "D0274", "D1110"],
}, },
bitewingsOnly: { newChildPatient: {
id: "bitewingsOnly", id: "newChildPatient",
label: "Bitewings Only", label: "New Child Patient",
codes: ["D0272"], 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… // add more…
}; };
// Which combos appear under which heading
export const COMBO_CATEGORIES: Record<string, (keyof typeof PROCEDURE_COMBOS)[]> = {
"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"],
};

View File

@@ -25,6 +25,7 @@ model User {
password String password String
patients Patient[] patients Patient[]
appointments Appointment[] appointments Appointment[]
staff Staff[]
claims Claim[] claims Claim[]
insuranceCredentials InsuranceCredential[] insuranceCredentials InsuranceCredential[]
updatedPayments Payment[] @relation("PaymentUpdatedBy") updatedPayments Payment[] @relation("PaymentUpdatedBy")
@@ -87,11 +88,13 @@ model Appointment {
model Staff { model Staff {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
userId Int?
name String name String
email String? email String?
role String // e.g., "Dentist", "Hygienist", "Assistant" role String // e.g., "Dentist", "Hygienist", "Assistant"
phone String? phone String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
appointments Appointment[] appointments Appointment[]
claims Claim[] @relation("ClaimStaff") claims Claim[] @relation("ClaimStaff")
} }

5
readmeForMigration.txt Normal file
View File

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