feat: wire copayment/adjustment into balance and collected calculations
Balance = totalBilled - mhPaidAmount - copayment - adjustment Collected = mhPaidAmount + copayment (adjustment is a write-off) - Frontend breakdown now shows Collected, Adjustment (if >0), and Balance - Reports: totalCollected and totalOutstanding use the new formula - Both date-range and staff summary queries updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -175,18 +175,19 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|||||||
)) as { cnt: number }[];
|
)) as { cnt: number }[];
|
||||||
const totalPatients = patientsCntRows?.[0]?.cnt ?? 0;
|
const totalPatients = patientsCntRows?.[0]?.cnt ?? 0;
|
||||||
|
|
||||||
// totalOutstanding: sum of (charges - paid - adjusted) across patients, using payments in range
|
// totalOutstanding: totalBilled - mhPaidAmount - copayment - adjustment
|
||||||
let outstandingSql = "";
|
let outstandingSql = "";
|
||||||
if (hasFrom && hasTo) {
|
if (hasFrom && hasTo) {
|
||||||
outstandingSql = `
|
outstandingSql = `
|
||||||
SELECT COALESCE(SUM(
|
SELECT COALESCE(SUM(
|
||||||
COALESCE(pm.total_charges,0) - COALESCE(pm.total_paid,0) - COALESCE(pm.total_adjusted,0)
|
GREATEST(COALESCE(pm.total_charges,0) - COALESCE(pm.mh_paid,0) - COALESCE(pm.copayment,0) - COALESCE(pm.adjustment,0), 0)
|
||||||
),0)::numeric(14,2) AS outstanding
|
),0)::numeric(14,2) AS outstanding
|
||||||
FROM (
|
FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
WHERE pay."createdAt" >= ${fromStart} AND pay."createdAt" <= ${toNextStart}
|
WHERE pay."createdAt" >= ${fromStart} AND pay."createdAt" <= ${toNextStart}
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
@@ -195,13 +196,14 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|||||||
} else if (hasFrom) {
|
} else if (hasFrom) {
|
||||||
outstandingSql = `
|
outstandingSql = `
|
||||||
SELECT COALESCE(SUM(
|
SELECT COALESCE(SUM(
|
||||||
COALESCE(pm.total_charges,0) - COALESCE(pm.total_paid,0) - COALESCE(pm.total_adjusted,0)
|
GREATEST(COALESCE(pm.total_charges,0) - COALESCE(pm.mh_paid,0) - COALESCE(pm.copayment,0) - COALESCE(pm.adjustment,0), 0)
|
||||||
),0)::numeric(14,2) AS outstanding
|
),0)::numeric(14,2) AS outstanding
|
||||||
FROM (
|
FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
WHERE pay."createdAt" >= ${fromStart}
|
WHERE pay."createdAt" >= ${fromStart}
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
@@ -210,13 +212,14 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|||||||
} else if (hasTo) {
|
} else if (hasTo) {
|
||||||
outstandingSql = `
|
outstandingSql = `
|
||||||
SELECT COALESCE(SUM(
|
SELECT COALESCE(SUM(
|
||||||
COALESCE(pm.total_charges,0) - COALESCE(pm.total_paid,0) - COALESCE(pm.total_adjusted,0)
|
GREATEST(COALESCE(pm.total_charges,0) - COALESCE(pm.mh_paid,0) - COALESCE(pm.copayment,0) - COALESCE(pm.adjustment,0), 0)
|
||||||
),0)::numeric(14,2) AS outstanding
|
),0)::numeric(14,2) AS outstanding
|
||||||
FROM (
|
FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
WHERE pay."createdAt" <= ${toNextStart}
|
WHERE pay."createdAt" <= ${toNextStart}
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
@@ -225,13 +228,14 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|||||||
} else {
|
} else {
|
||||||
outstandingSql = `
|
outstandingSql = `
|
||||||
SELECT COALESCE(SUM(
|
SELECT COALESCE(SUM(
|
||||||
COALESCE(pm.total_charges,0) - COALESCE(pm.total_paid,0) - COALESCE(pm.total_adjusted,0)
|
GREATEST(COALESCE(pm.total_charges,0) - COALESCE(pm.mh_paid,0) - COALESCE(pm.copayment,0) - COALESCE(pm.adjustment,0), 0)
|
||||||
),0)::numeric(14,2) AS outstanding
|
),0)::numeric(14,2) AS outstanding
|
||||||
FROM (
|
FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
) pm
|
) pm
|
||||||
@@ -242,74 +246,78 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|||||||
)) as { outstanding: string }[];
|
)) as { outstanding: string }[];
|
||||||
const totalOutstanding = Number(outstandingRows?.[0]?.outstanding ?? 0);
|
const totalOutstanding = Number(outstandingRows?.[0]?.outstanding ?? 0);
|
||||||
|
|
||||||
// totalCollected: sum(totalPaid) in the range
|
// totalCollected: mhPaidAmount + copayment (adjustment is write-off, not collected)
|
||||||
let collSql = "";
|
let collSql = "";
|
||||||
if (hasFrom && hasTo) {
|
if (hasFrom && hasTo) {
|
||||||
collSql = `SELECT COALESCE(SUM("totalPaid"),0)::numeric(14,2) AS collected FROM "Payment" WHERE "createdAt" >= ${fromStart} AND "createdAt" <= ${toNextStart}`;
|
collSql = `SELECT COALESCE(SUM(COALESCE("mhPaidAmount",0) + "copayment"),0)::numeric(14,2) AS collected FROM "Payment" WHERE "createdAt" >= ${fromStart} AND "createdAt" <= ${toNextStart}`;
|
||||||
} else if (hasFrom) {
|
} else if (hasFrom) {
|
||||||
collSql = `SELECT COALESCE(SUM("totalPaid"),0)::numeric(14,2) AS collected FROM "Payment" WHERE "createdAt" >= ${fromStart}`;
|
collSql = `SELECT COALESCE(SUM(COALESCE("mhPaidAmount",0) + "copayment"),0)::numeric(14,2) AS collected FROM "Payment" WHERE "createdAt" >= ${fromStart}`;
|
||||||
} else if (hasTo) {
|
} else if (hasTo) {
|
||||||
collSql = `SELECT COALESCE(SUM("totalPaid"),0)::numeric(14,2) AS collected FROM "Payment" WHERE "createdAt" <= ${toNextStart}`;
|
collSql = `SELECT COALESCE(SUM(COALESCE("mhPaidAmount",0) + "copayment"),0)::numeric(14,2) AS collected FROM "Payment" WHERE "createdAt" <= ${toNextStart}`;
|
||||||
} else {
|
} else {
|
||||||
collSql = `SELECT COALESCE(SUM("totalPaid"),0)::numeric(14,2) AS collected FROM "Payment"`;
|
collSql = `SELECT COALESCE(SUM(COALESCE("mhPaidAmount",0) + "copayment"),0)::numeric(14,2) AS collected FROM "Payment"`;
|
||||||
}
|
}
|
||||||
const collRows = (await prisma.$queryRawUnsafe(collSql)) as {
|
const collRows = (await prisma.$queryRawUnsafe(collSql)) as {
|
||||||
collected: string;
|
collected: string;
|
||||||
}[];
|
}[];
|
||||||
const totalCollected = Number(collRows?.[0]?.collected ?? 0);
|
const totalCollected = Number(collRows?.[0]?.collected ?? 0);
|
||||||
|
|
||||||
// NEW: patientsWithBalance: number of patients whose (charges - paid - adjusted) > 0, within the date range
|
// patientsWithBalance: patients where (billed - mhPaid - copayment - adjustment) > 0
|
||||||
let patientsWithBalanceSql = "";
|
let patientsWithBalanceSql = "";
|
||||||
if (hasFrom && hasTo) {
|
if (hasFrom && hasTo) {
|
||||||
patientsWithBalanceSql = `
|
patientsWithBalanceSql = `
|
||||||
SELECT COUNT(*)::int AS cnt FROM (
|
SELECT COUNT(*)::int AS cnt FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
WHERE pay."createdAt" >= ${fromStart} AND pay."createdAt" <= ${toNextStart}
|
WHERE pay."createdAt" >= ${fromStart} AND pay."createdAt" <= ${toNextStart}
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
) t
|
) t
|
||||||
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.total_paid,0) - COALESCE(t.total_adjusted,0)) > 0
|
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.mh_paid,0) - COALESCE(t.copayment,0) - COALESCE(t.adjustment,0)) > 0
|
||||||
`;
|
`;
|
||||||
} else if (hasFrom) {
|
} else if (hasFrom) {
|
||||||
patientsWithBalanceSql = `
|
patientsWithBalanceSql = `
|
||||||
SELECT COUNT(*)::int AS cnt FROM (
|
SELECT COUNT(*)::int AS cnt FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
WHERE pay."createdAt" >= ${fromStart}
|
WHERE pay."createdAt" >= ${fromStart}
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
) t
|
) t
|
||||||
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.total_paid,0) - COALESCE(t.total_adjusted,0)) > 0
|
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.mh_paid,0) - COALESCE(t.copayment,0) - COALESCE(t.adjustment,0)) > 0
|
||||||
`;
|
`;
|
||||||
} else if (hasTo) {
|
} else if (hasTo) {
|
||||||
patientsWithBalanceSql = `
|
patientsWithBalanceSql = `
|
||||||
SELECT COUNT(*)::int AS cnt FROM (
|
SELECT COUNT(*)::int AS cnt FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
WHERE pay."createdAt" <= ${toNextStart}
|
WHERE pay."createdAt" <= ${toNextStart}
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
) t
|
) t
|
||||||
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.total_paid,0) - COALESCE(t.total_adjusted,0)) > 0
|
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.mh_paid,0) - COALESCE(t.copayment,0) - COALESCE(t.adjustment,0)) > 0
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
patientsWithBalanceSql = `
|
patientsWithBalanceSql = `
|
||||||
SELECT COUNT(*)::int AS cnt FROM (
|
SELECT COUNT(*)::int AS cnt FROM (
|
||||||
SELECT pay."patientId" AS patient_id,
|
SELECT pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
GROUP BY pay."patientId"
|
GROUP BY pay."patientId"
|
||||||
) t
|
) t
|
||||||
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.total_paid,0) - COALESCE(t.total_adjusted,0)) > 0
|
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.mh_paid,0) - COALESCE(t.copayment,0) - COALESCE(t.adjustment,0)) > 0
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
const pwbRows = (await prisma.$queryRawUnsafe(
|
const pwbRows = (await prisma.$queryRawUnsafe(
|
||||||
@@ -825,8 +833,9 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|||||||
SELECT
|
SELECT
|
||||||
pay."patientId" AS patient_id,
|
pay."patientId" AS patient_id,
|
||||||
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
||||||
SUM(pay."totalPaid")::numeric(14,2) AS total_paid,
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
||||||
SUM(pay."totalAdjusted")::numeric(14,2) AS total_adjusted
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
||||||
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
||||||
FROM "Payment" pay
|
FROM "Payment" pay
|
||||||
JOIN "Claim" c ON pay."claimId" = c.id
|
JOIN "Claim" c ON pay."claimId" = c.id
|
||||||
WHERE c."staffId" = ${Number(staffId)}
|
WHERE c."staffId" = ${Number(staffId)}
|
||||||
@@ -835,9 +844,9 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|||||||
)
|
)
|
||||||
SELECT json_build_object(
|
SELECT json_build_object(
|
||||||
'totalPatients', COALESCE(COUNT(DISTINCT pa.patient_id),0),
|
'totalPatients', COALESCE(COUNT(DISTINCT pa.patient_id),0),
|
||||||
'totalOutstanding', COALESCE(SUM(COALESCE(pa.total_charges,0) - COALESCE(pa.total_paid,0) - COALESCE(pa.total_adjusted,0)),0)::text,
|
'totalOutstanding', COALESCE(SUM(GREATEST(COALESCE(pa.total_charges,0) - COALESCE(pa.mh_paid,0) - COALESCE(pa.copayment,0) - COALESCE(pa.adjustment,0), 0)),0)::text,
|
||||||
'totalCollected', COALESCE(SUM(COALESCE(pa.total_paid,0)),0)::text,
|
'totalCollected', COALESCE(SUM(COALESCE(pa.mh_paid,0) + COALESCE(pa.copayment,0)),0)::text,
|
||||||
'patientsWithBalance', COALESCE(SUM(CASE WHEN (COALESCE(pa.total_charges,0) - COALESCE(pa.total_paid,0) - COALESCE(pa.total_adjusted,0)) > 0 THEN 1 ELSE 0 END),0)
|
'patientsWithBalance', COALESCE(SUM(CASE WHEN (COALESCE(pa.total_charges,0) - COALESCE(pa.mh_paid,0) - COALESCE(pa.copayment,0) - COALESCE(pa.adjustment,0)) > 0 THEN 1 ELSE 0 END),0)
|
||||||
) AS summary_json
|
) AS summary_json
|
||||||
FROM payments_agg pa;
|
FROM payments_agg pa;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -634,7 +634,11 @@ export default function PaymentsRecentTable({
|
|||||||
paymentsData?.payments.map((payment) => {
|
paymentsData?.payments.map((payment) => {
|
||||||
const totalBilled = Number(payment.totalBilled || 0);
|
const totalBilled = Number(payment.totalBilled || 0);
|
||||||
const totalPaid = Number(payment.totalPaid || 0);
|
const totalPaid = Number(payment.totalPaid || 0);
|
||||||
const totalDue = Number(payment.totalDue || 0);
|
const mhPaid = Number(payment.mhPaidAmount || 0);
|
||||||
|
const copayment = Number(payment.copayment || 0);
|
||||||
|
const adjustment = Number(payment.adjustment || 0);
|
||||||
|
const totalDue = Math.max(0, totalBilled - mhPaid - copayment - adjustment);
|
||||||
|
const totalCollected = mhPaid + copayment;
|
||||||
|
|
||||||
const displayName = getName(payment);
|
const displayName = getName(payment);
|
||||||
const submittedOn =
|
const submittedOn =
|
||||||
@@ -682,22 +686,25 @@ export default function PaymentsRecentTable({
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
{/* 💰 Billed / Paid / Due breakdown */}
|
{/* 💰 Billed / Collected / Due breakdown */}
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span>
|
<span>
|
||||||
<strong>Total Billed:</strong> $
|
<strong>Total Billed:</strong> ${totalBilled.toFixed(2)}
|
||||||
{Number(totalBilled).toFixed(2)}
|
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<strong>Total Paid:</strong> ${totalPaid.toFixed(2)}
|
<strong>Collected:</strong> ${totalCollected.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
|
{adjustment > 0 && (
|
||||||
<span>
|
<span>
|
||||||
<strong>Total Due:</strong>{" "}
|
<strong>Adjustment:</strong>{" "}
|
||||||
|
<span className="text-orange-600">-${adjustment.toFixed(2)}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
<strong>Balance:</strong>{" "}
|
||||||
{totalDue > 0 ? (
|
{totalDue > 0 ? (
|
||||||
<span className="text-yellow-600">
|
<span className="text-yellow-600">${totalDue.toFixed(2)}</span>
|
||||||
${totalDue.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
) : (
|
) : (
|
||||||
<span className="text-green-600">Settled</span>
|
<span className="text-green-600">Settled</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user