routes fixed - user based fixed done
This commit is contained in:
@@ -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" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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> => {
|
||||||
|
try {
|
||||||
const userId = (req as any).user?.id;
|
const userId = (req as any).user?.id;
|
||||||
const notifications = await prisma.notification.findMany({
|
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||||
where: { userId },
|
|
||||||
orderBy: { createdAt: "desc" },
|
const notifications = await storage.getNotifications(userId, 20, 0);
|
||||||
take: 20,
|
|
||||||
});
|
|
||||||
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
|
||||||
|
router.post("/:id/read", async (req: Request, res: Response): Promise<any> => {
|
||||||
|
try {
|
||||||
const userId = (req as any).user?.id;
|
const userId = (req as any).user?.id;
|
||||||
await prisma.notification.updateMany({
|
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||||
where: { id: Number(req.params.id), userId },
|
|
||||||
data: { read: 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 });
|
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
|
||||||
|
router.post("/read-all", async (req: Request, res: Response): Promise<any> => {
|
||||||
|
try {
|
||||||
const userId = (req as any).user?.id;
|
const userId = (req as any).user?.id;
|
||||||
await prisma.notification.updateMany({
|
if (!userId) return res.status(401).json({ message: "Unauthorized" });
|
||||||
where: { userId },
|
|
||||||
data: { read: true },
|
const count = await storage.markAllNotificationsRead(userId);
|
||||||
});
|
res.json({ success: true, updatedCount: count });
|
||||||
res.json({ success: true });
|
} 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;
|
||||||
|
|||||||
@@ -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,9 +105,10 @@ 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(
|
||||||
|
"/by-insurance-id",
|
||||||
|
async (req: Request, res: Response): Promise<any> => {
|
||||||
const insuranceId = req.query.insuranceId?.toString();
|
const insuranceId = req.query.insuranceId?.toString();
|
||||||
|
|
||||||
if (!insuranceId) {
|
if (!insuranceId) {
|
||||||
@@ -180,7 +127,8 @@ router.get("/by-insurance-id", async (req: Request, res: Response): Promise<any>
|
|||||||
console.error("Failed to lookup patient:", err);
|
console.error("Failed to lookup patient:", err);
|
||||||
return res.status(500).json({ error: "Internal server error" });
|
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",
|
||||||
|
|||||||
@@ -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" });
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,8 +708,17 @@ 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]) => (
|
||||||
|
<div key={section}>
|
||||||
|
<div className="mb-3 text-sm font-semibold opacity-70">
|
||||||
|
{section}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{ids.map((id) => {
|
||||||
|
const b = PROCEDURE_COMBOS[id];
|
||||||
|
if(!b){return}
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={b.id}
|
key={b.id}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -728,13 +738,21 @@ export function ClaimForm({
|
|||||||
>
|
>
|
||||||
{b.label}
|
{b.label}
|
||||||
</Button>
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<div className="pt-4">
|
||||||
<Button variant="success" onClick={onMapPrice}>
|
<Button variant="success" onClick={onMapPrice}>
|
||||||
Map Price
|
Map Price
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* File Upload Section */}
|
{/* File Upload Section */}
|
||||||
<div className="mt-4 bg-gray-100 p-4 rounded-md space-y-4">
|
<div className="mt-4 bg-gray-100 p-4 rounded-md space-y-4">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
};
|
||||||
|
|||||||
@@ -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
5
readmeForMigration.txt
Normal 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.
|
||||||
Reference in New Issue
Block a user