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:
Gitead
2026-05-06 21:36:04 -04:00
parent 4bd501250d
commit 49415bcfc4
2 changed files with 62 additions and 46 deletions

View File

@@ -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;
`; `;

View File

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