route added
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user