From 796923444539622f1085d42825a8406236d62867 Mon Sep 17 00:00:00 2001 From: Potenz Date: Thu, 14 Aug 2025 00:04:47 +0530 Subject: [PATCH] route added --- apps/Backend/src/routes/payments.ts | 111 ++++++++++++++++-- .../payments/payments-recent-table.tsx | 2 +- packages/db/prisma/schema.prisma | 25 ++-- packages/db/types/payment-types.ts | 39 +++--- 4 files changed, 134 insertions(+), 43 deletions(-) diff --git a/apps/Backend/src/routes/payments.ts b/apps/Backend/src/routes/payments.ts index 6eadbe9..74bd43a 100644 --- a/apps/Backend/src/routes/payments.ts +++ b/apps/Backend/src/routes/payments.ts @@ -3,7 +3,14 @@ import { Request, Response } from "express"; import { storage } from "../storage"; import { z } from "zod"; import { ZodError } from "zod"; -import { insertPaymentSchema, updatePaymentSchema } from "@repo/db/types"; +import { + insertPaymentSchema, + NewTransactionPayload, + newTransactionPayloadSchema, + updatePaymentSchema, +} from "@repo/db/types"; +import Decimal from "decimal.js"; +import { prisma } from "@repo/db/client"; const paymentFilterSchema = z.object({ from: z.string().datetime(), @@ -146,8 +153,7 @@ router.get("/:id", async (req: Request, res: Response): Promise => { const id = parseIntOrError(req.params.id, "Payment ID"); const payment = await storage.getPaymentById(userId, id); - if (!payment) - return res.status(404).json({ message: "Payment not found" }); + if (!payment) return res.status(404).json({ message: "Payment not found" }); res.status(200).json(payment); } catch (err: unknown) { @@ -157,7 +163,6 @@ router.get("/:id", async (req: Request, res: Response): Promise => { } }); - // POST /api/payments/:claimId router.post("/:claimId", async (req: Request, res: Response): Promise => { try { @@ -194,9 +199,11 @@ router.put("/:id", async (req: Request, res: Response): Promise => { const userId = req.user?.id; if (!userId) return res.status(401).json({ message: "Unauthorized" }); - const id = parseIntOrError(req.params.id, "Payment ID"); + const paymentId = parseIntOrError(req.params.id, "Payment ID"); - const validated = updatePaymentSchema.safeParse(req.body); + const validated = newTransactionPayloadSchema.safeParse( + req.body.data as NewTransactionPayload + ); if (!validated.success) { return res.status(400).json({ message: "Validation failed", @@ -204,9 +211,97 @@ router.put("/:id", async (req: Request, res: Response): Promise => { }); } - const updated = await storage.updatePayment(id, validated.data, userId); + const { status, serviceLineTransactions } = validated.data; - res.status(200).json(updated); + // Wrap everything in a transaction + const result = await prisma.$transaction(async (tx) => { + // 1. Create all new service line transactions + for (const txn of serviceLineTransactions) { + await tx.serviceLineTransaction.create({ + data: { + paymentId, + serviceLineId: txn.serviceLineId, + transactionId: txn.transactionId, + paidAmount: new Decimal(txn.paidAmount || 0), + adjustedAmount: new Decimal(txn.adjustedAmount || 0), + method: txn.method, + receivedDate: txn.receivedDate, + payerName: txn.payerName, + notes: txn.notes, + }, + }); + + // 2. Recalculate that specific service line's totals + const aggLine = await tx.serviceLineTransaction.aggregate({ + _sum: { + paidAmount: true, + adjustedAmount: true, + }, + where: { serviceLineId: txn.serviceLineId }, + }); + + const serviceLine = await tx.serviceLine.findUniqueOrThrow({ + where: { id: txn.serviceLineId }, + select: { totalBilled: true }, + }); + + const totalPaid = aggLine._sum.paidAmount || new Decimal(0); + const totalAdjusted = aggLine._sum.adjustedAmount || new Decimal(0); + const totalDue = serviceLine.totalBilled + .minus(totalPaid) + .minus(totalAdjusted); + + await tx.serviceLine.update({ + where: { id: txn.serviceLineId }, + data: { + totalPaid, + totalAdjusted, + totalDue, + status: + totalDue.lte(0) && totalPaid.gt(0) + ? "PAID" + : totalPaid.gt(0) + ? "PARTIALLY_PAID" + : "UNPAID", + }, + }); + } + + // 3. Recalculate payment totals + const aggPayment = await tx.serviceLineTransaction.aggregate({ + _sum: { + paidAmount: true, + adjustedAmount: true, + }, + where: { paymentId }, + }); + + const payment = await tx.payment.findUniqueOrThrow({ + where: { id: paymentId }, + select: { totalBilled: true }, + }); + + const totalPaid = aggPayment._sum.paidAmount || new Decimal(0); + const totalAdjusted = aggPayment._sum.adjustedAmount || new Decimal(0); + const totalDue = payment.totalBilled + .minus(totalPaid) + .minus(totalAdjusted); + + const updatedPayment = await tx.payment.update({ + where: { id: paymentId }, + data: { + totalPaid, + totalAdjusted, + totalDue, + status, + updatedById: userId, + }, + }); + + return updatedPayment; + }); + + res.status(200).json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : "Failed to update payment"; diff --git a/apps/Frontend/src/components/payments/payments-recent-table.tsx b/apps/Frontend/src/components/payments/payments-recent-table.tsx index b4b418f..a8a3825 100644 --- a/apps/Frontend/src/components/payments/payments-recent-table.tsx +++ b/apps/Frontend/src/components/payments/payments-recent-table.tsx @@ -120,7 +120,7 @@ export default function PaymentsRecentTable({ mutationFn: async (data: NewTransactionPayload) => { const response = await apiRequest( "PUT", - `/api/claims/${data.paymentId}`, + `/api/payments/${data.paymentId}`, { data: data, } diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index e2b1509..cbb82c7 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -199,18 +199,19 @@ enum PdfCategory { } model Payment { - id Int @id @default(autoincrement()) - claimId Int @unique - patientId Int - userId Int - updatedById Int? - totalBilled Decimal @db.Decimal(10, 2) - totalPaid Decimal @default(0.00) @db.Decimal(10, 2) - totalDue Decimal @db.Decimal(10, 2) - status PaymentStatus @default(PENDING) - notes String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id Int @id @default(autoincrement()) + claimId Int @unique + patientId Int + userId Int + updatedById Int? + totalBilled Decimal @db.Decimal(10, 2) + totalPaid Decimal @default(0.00) @db.Decimal(10, 2) + totalAdjusted Decimal @default(0.00) @db.Decimal(10, 2) + totalDue Decimal @db.Decimal(10, 2) + status PaymentStatus @default(PENDING) + notes String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade) patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade) diff --git a/packages/db/types/payment-types.ts b/packages/db/types/payment-types.ts index 6c97e43..0e5f6b6 100644 --- a/packages/db/types/payment-types.ts +++ b/packages/db/types/payment-types.ts @@ -60,14 +60,6 @@ export const updatePaymentSchema = ( .partial(); export type UpdatePayment = z.infer; -// Input for updating a payment with new transactions + updated service payments -export type UpdatePaymentInput = { - newTransactions: ServiceLineTransactionInput[]; - totalPaid: Decimal; - totalDue: Decimal; - status: PaymentStatus; -}; - // ========== EXTENDED TYPES ========== // Payment with full nested data @@ -91,18 +83,21 @@ export type PaymentWithExtras = Prisma.PaymentGetPayload<{ paymentMethod: string; }; +export const newTransactionPayloadSchema = z.object({ + paymentId: z.number(), + status: PaymentStatusSchema, + serviceLineTransactions: z.array( + z.object({ + serviceLineId: z.number(), + transactionId: z.string().optional(), + paidAmount: z.number(), + adjustedAmount: z.number().optional(), + method: PaymentMethodSchema, + receivedDate: z.coerce.date(), + payerName: z.string().optional(), + notes: z.string().optional(), + }) + ), +}); -export type NewTransactionPayload = { - paymentId: number; - status: PaymentStatus; - serviceLineTransactions: { - serviceLineId: number; - transactionId?: string; - paidAmount: number; - adjustedAmount?: number; - method: PaymentMethod; - receivedDate: Date; - payerName?: string; - notes?: string; - }[]; -}; +export type NewTransactionPayload = z.infer;