route added

This commit is contained in:
2025-08-14 00:04:47 +05:30
parent 092201971b
commit 7969234445
4 changed files with 134 additions and 43 deletions

View File

@@ -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<any> => {
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<any> => {
}
});
// POST /api/payments/:claimId
router.post("/:claimId", async (req: Request, res: Response): Promise<any> => {
try {
@@ -194,9 +199,11 @@ router.put("/:id", async (req: Request, res: Response): Promise<any> => {
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<any> => {
});
}
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";

View File

@@ -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,
}

View File

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

View File

@@ -60,14 +60,6 @@ export const updatePaymentSchema = (
.partial();
export type UpdatePayment = z.infer<typeof updatePaymentSchema>;
// 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<typeof newTransactionPayloadSchema>;