|
|
|
|
@@ -24,6 +24,14 @@ export interface IPaymentsReportsStorage {
|
|
|
|
|
npiProviderId?: number | null
|
|
|
|
|
): Promise<GetPatientBalancesResult>;
|
|
|
|
|
|
|
|
|
|
getPatientsWithZeroBalance(
|
|
|
|
|
limit: number,
|
|
|
|
|
cursorToken?: string | null,
|
|
|
|
|
from?: Date | null,
|
|
|
|
|
to?: Date | null,
|
|
|
|
|
npiProviderId?: number | null
|
|
|
|
|
): Promise<GetPatientBalancesResult & { totalCharges: number; totalCollected: number }>;
|
|
|
|
|
|
|
|
|
|
getPatientsBalancesByDoctor(
|
|
|
|
|
staffId: number,
|
|
|
|
|
limit: number,
|
|
|
|
|
@@ -136,47 +144,35 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
const patientsCntRows = (await prisma.$queryRawUnsafe(patientsCountSql)) as { cnt: number }[];
|
|
|
|
|
const totalPatients = patientsCntRows?.[0]?.cnt ?? 0;
|
|
|
|
|
|
|
|
|
|
const outstandingSql = `
|
|
|
|
|
SELECT COALESCE(SUM(
|
|
|
|
|
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
|
|
|
|
|
// effective_balance per payment:
|
|
|
|
|
// 0 if status='PAID' (user confirmed Pay In Full)
|
|
|
|
|
// billed-collected if status!='PAID' and collected < billed
|
|
|
|
|
// A patient has zero balance only when sum(effective_balance) = 0.
|
|
|
|
|
const combinedSql = `
|
|
|
|
|
SELECT
|
|
|
|
|
COALESCE(SUM(pm.effective_balance),0)::numeric(14,2) AS outstanding,
|
|
|
|
|
COALESCE(SUM(pm.total_collected),0)::numeric(14,2) AS collected,
|
|
|
|
|
COUNT(CASE WHEN pm.effective_balance > 0 THEN 1 END)::int AS patients_with_balance
|
|
|
|
|
FROM (
|
|
|
|
|
SELECT pay."patientId" AS patient_id,
|
|
|
|
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
|
|
|
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
|
|
|
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
|
|
|
|
SELECT
|
|
|
|
|
pay."patientId" AS patient_id,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment")::numeric(14,2) AS total_collected,
|
|
|
|
|
SUM(
|
|
|
|
|
CASE WHEN pay.status = 'PAID' THEN 0
|
|
|
|
|
ELSE GREATEST(pay."totalBilled" - COALESCE(pay."mhPaidAmount",0) - pay."copayment", 0)
|
|
|
|
|
END
|
|
|
|
|
)::numeric(14,2) AS effective_balance
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
${payWhereClause}
|
|
|
|
|
GROUP BY pay."patientId"
|
|
|
|
|
) pm
|
|
|
|
|
`;
|
|
|
|
|
const outstandingRows = (await prisma.$queryRawUnsafe(outstandingSql)) as { outstanding: string }[];
|
|
|
|
|
const totalOutstanding = Number(outstandingRows?.[0]?.outstanding ?? 0);
|
|
|
|
|
|
|
|
|
|
const collSql = `
|
|
|
|
|
SELECT COALESCE(SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment"),0)::numeric(14,2) AS collected
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
${payWhereClause}
|
|
|
|
|
`;
|
|
|
|
|
const collRows = (await prisma.$queryRawUnsafe(collSql)) as { collected: string }[];
|
|
|
|
|
const totalCollected = Number(collRows?.[0]?.collected ?? 0);
|
|
|
|
|
|
|
|
|
|
const patientsWithBalanceSql = `
|
|
|
|
|
SELECT COUNT(*)::int AS cnt FROM (
|
|
|
|
|
SELECT pay."patientId" AS patient_id,
|
|
|
|
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
|
|
|
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
|
|
|
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
${payWhereClause}
|
|
|
|
|
GROUP BY pay."patientId"
|
|
|
|
|
) t
|
|
|
|
|
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(patientsWithBalanceSql)) as { cnt: number }[];
|
|
|
|
|
const patientsWithBalance = pwbRows?.[0]?.cnt ?? 0;
|
|
|
|
|
const combinedRows = (await prisma.$queryRawUnsafe(combinedSql)) as {
|
|
|
|
|
outstanding: string; collected: string; patients_with_balance: number;
|
|
|
|
|
}[];
|
|
|
|
|
const totalOutstanding = Number(combinedRows?.[0]?.outstanding ?? 0);
|
|
|
|
|
const totalCollected = Number(combinedRows?.[0]?.collected ?? 0);
|
|
|
|
|
const patientsWithBalance = combinedRows?.[0]?.patients_with_balance ?? 0;
|
|
|
|
|
|
|
|
|
|
return { totalPatients, totalOutstanding, totalCollected, patientsWithBalance };
|
|
|
|
|
} catch (err) {
|
|
|
|
|
@@ -236,6 +232,11 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
SUM(pay."totalBilled")::numeric(12,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment")::numeric(12,2) AS total_paid,
|
|
|
|
|
SUM(pay."adjustment")::numeric(12,2) AS total_adjusted,
|
|
|
|
|
SUM(
|
|
|
|
|
CASE WHEN pay.status = 'PAID' THEN 0
|
|
|
|
|
ELSE GREATEST(pay."totalBilled" - COALESCE(pay."mhPaidAmount",0) - pay."copayment", 0)
|
|
|
|
|
END
|
|
|
|
|
)::numeric(12,2) AS effective_balance,
|
|
|
|
|
MAX(pay."createdAt") AS last_payment_date
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
${paymentWhereClause}
|
|
|
|
|
@@ -243,9 +244,6 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
) pm
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// Build keyset predicate if cursor provided.
|
|
|
|
|
// Ordering used: pm.last_payment_date DESC NULLS LAST, p."createdAt" DESC, p.id DESC
|
|
|
|
|
// For keyset, we need to fetch rows strictly "less than" the cursor in this ordering.
|
|
|
|
|
let keysetPredicate = "";
|
|
|
|
|
if (cursor) {
|
|
|
|
|
const lp = cursor.lastPaymentDate
|
|
|
|
|
@@ -253,17 +251,13 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
: "NULL";
|
|
|
|
|
const id = Number(cursor.lastPatientId);
|
|
|
|
|
|
|
|
|
|
// We handle NULL last_payment_date ordering: since we use "NULLS LAST" in ORDER BY,
|
|
|
|
|
// rows with last_payment_date = NULL are considered *after* any non-null dates.
|
|
|
|
|
// To page correctly when cursor's lastPaymentDate is null, we compare accordingly.
|
|
|
|
|
// This predicate tries to cover both cases.
|
|
|
|
|
keysetPredicate = `
|
|
|
|
|
AND (
|
|
|
|
|
(pm.last_payment_date IS NOT NULL AND ${lp} IS NOT NULL AND (
|
|
|
|
|
pm.last_payment_date < ${lp}
|
|
|
|
|
OR (pm.last_payment_date = ${lp} AND p.id < ${id})
|
|
|
|
|
))
|
|
|
|
|
OR (pm.last_payment_date IS NULL AND ${lp} IS NOT NULL)
|
|
|
|
|
OR (pm.last_payment_date IS NULL AND ${lp} IS NOT NULL)
|
|
|
|
|
OR (pm.last_payment_date IS NULL AND ${lp} IS NULL AND p.id < ${id})
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
@@ -277,7 +271,7 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
COALESCE(pm.total_charges,0)::numeric(12,2) AS total_charges,
|
|
|
|
|
COALESCE(pm.total_paid,0)::numeric(12,2) AS total_paid,
|
|
|
|
|
COALESCE(pm.total_adjusted,0)::numeric(12,2) AS total_adjusted,
|
|
|
|
|
(COALESCE(pm.total_charges,0) - COALESCE(pm.total_paid,0) - COALESCE(pm.total_adjusted,0))::numeric(12,2) AS current_balance,
|
|
|
|
|
COALESCE(pm.effective_balance,0)::numeric(12,2) AS current_balance,
|
|
|
|
|
pm.last_payment_date,
|
|
|
|
|
apt.last_appointment_date
|
|
|
|
|
FROM "Patient" p
|
|
|
|
|
@@ -287,7 +281,7 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
FROM "Appointment"
|
|
|
|
|
GROUP BY "patientId"
|
|
|
|
|
) apt ON apt.patient_id = p.id
|
|
|
|
|
WHERE (COALESCE(pm.total_charges,0) - COALESCE(pm.total_paid,0) - COALESCE(pm.total_adjusted,0)) > 0
|
|
|
|
|
WHERE COALESCE(pm.effective_balance,0) > 0
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const orderBy = `ORDER BY pm.last_payment_date DESC NULLS LAST, p.id DESC`;
|
|
|
|
|
@@ -350,18 +344,19 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
: null,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// totalCount: count of patients with positive balance within same payment date filter
|
|
|
|
|
const countSql = `
|
|
|
|
|
SELECT COUNT(*)::int AS cnt FROM (
|
|
|
|
|
SELECT pay."patientId" AS patient_id,
|
|
|
|
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment")::numeric(14,2) AS total_paid,
|
|
|
|
|
SUM(pay."adjustment")::numeric(14,2) AS total_adjusted
|
|
|
|
|
SELECT
|
|
|
|
|
SUM(
|
|
|
|
|
CASE WHEN pay.status = 'PAID' THEN 0
|
|
|
|
|
ELSE GREATEST(pay."totalBilled" - COALESCE(pay."mhPaidAmount",0) - pay."copayment", 0)
|
|
|
|
|
END
|
|
|
|
|
)::numeric(14,2) AS effective_balance
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
${paymentWhereClause}
|
|
|
|
|
GROUP BY pay."patientId"
|
|
|
|
|
) t
|
|
|
|
|
WHERE (COALESCE(t.total_charges,0) - COALESCE(t.total_paid,0) - COALESCE(t.total_adjusted,0)) > 0;
|
|
|
|
|
WHERE t.effective_balance > 0;
|
|
|
|
|
`;
|
|
|
|
|
const cntRows = (await prisma.$queryRawUnsafe(countSql)) as {
|
|
|
|
|
cnt: number;
|
|
|
|
|
@@ -380,6 +375,169 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns patients whose current balance is zero or negative (fully paid/over-paid).
|
|
|
|
|
*/
|
|
|
|
|
async getPatientsWithZeroBalance(
|
|
|
|
|
limit = 25,
|
|
|
|
|
cursorToken?: string | null,
|
|
|
|
|
from?: Date | null,
|
|
|
|
|
to?: Date | null,
|
|
|
|
|
npiProviderId?: number | null
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
type RawRow = {
|
|
|
|
|
patient_id: number;
|
|
|
|
|
first_name: string | null;
|
|
|
|
|
last_name: string | null;
|
|
|
|
|
total_charges: string;
|
|
|
|
|
total_paid: string;
|
|
|
|
|
total_adjusted: string;
|
|
|
|
|
current_balance: string;
|
|
|
|
|
last_payment_date: Date | null;
|
|
|
|
|
last_appointment_date: Date | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const safeLimit = Math.max(1, Math.min(200, Number(limit) || 25));
|
|
|
|
|
const cursor = decodeCursor(cursorToken);
|
|
|
|
|
|
|
|
|
|
const hasFrom = from !== undefined && from !== null;
|
|
|
|
|
const hasTo = to !== undefined && to !== null;
|
|
|
|
|
|
|
|
|
|
const fromStart = isoStartOfDayLiteral(from);
|
|
|
|
|
const toNextStart = isoStartOfNextDayLiteral(to);
|
|
|
|
|
|
|
|
|
|
const payConditions: string[] = [];
|
|
|
|
|
if (hasFrom) payConditions.push(`pay."createdAt" >= ${fromStart}`);
|
|
|
|
|
if (hasTo) payConditions.push(`pay."createdAt" <= ${toNextStart}`);
|
|
|
|
|
if (npiProviderId) payConditions.push(`pay."npiProviderId" = ${Number(npiProviderId)}`);
|
|
|
|
|
const paymentWhereClause = payConditions.length ? `WHERE ${payConditions.join(" AND ")}` : "";
|
|
|
|
|
|
|
|
|
|
const pmSubquery = `
|
|
|
|
|
(
|
|
|
|
|
SELECT
|
|
|
|
|
pay."patientId" AS patient_id,
|
|
|
|
|
SUM(pay."totalBilled")::numeric(12,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment")::numeric(12,2) AS total_paid,
|
|
|
|
|
SUM(pay."adjustment")::numeric(12,2) AS total_adjusted,
|
|
|
|
|
SUM(
|
|
|
|
|
CASE WHEN pay.status = 'PAID' THEN 0
|
|
|
|
|
ELSE GREATEST(pay."totalBilled" - COALESCE(pay."mhPaidAmount",0) - pay."copayment", 0)
|
|
|
|
|
END
|
|
|
|
|
)::numeric(12,2) AS effective_balance,
|
|
|
|
|
MAX(pay."createdAt") AS last_payment_date
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
${paymentWhereClause}
|
|
|
|
|
GROUP BY pay."patientId"
|
|
|
|
|
) pm
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
let keysetPredicate = "";
|
|
|
|
|
if (cursor) {
|
|
|
|
|
const lp = cursor.lastPaymentDate ? `'${cursor.lastPaymentDate}'` : "NULL";
|
|
|
|
|
const id = Number(cursor.lastPatientId);
|
|
|
|
|
keysetPredicate = `
|
|
|
|
|
AND (
|
|
|
|
|
(pm.last_payment_date IS NOT NULL AND ${lp} IS NOT NULL AND (
|
|
|
|
|
pm.last_payment_date < ${lp}
|
|
|
|
|
OR (pm.last_payment_date = ${lp} AND p.id < ${id})
|
|
|
|
|
))
|
|
|
|
|
OR (pm.last_payment_date IS NULL AND ${lp} IS NOT NULL)
|
|
|
|
|
OR (pm.last_payment_date IS NULL AND ${lp} IS NULL AND p.id < ${id})
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const baseSelect = `
|
|
|
|
|
SELECT
|
|
|
|
|
p.id AS patient_id,
|
|
|
|
|
p."firstName" AS first_name,
|
|
|
|
|
p."lastName" AS last_name,
|
|
|
|
|
COALESCE(pm.total_charges,0)::numeric(12,2) AS total_charges,
|
|
|
|
|
COALESCE(pm.total_paid,0)::numeric(12,2) AS total_paid,
|
|
|
|
|
COALESCE(pm.total_adjusted,0)::numeric(12,2) AS total_adjusted,
|
|
|
|
|
0::numeric(12,2) AS current_balance,
|
|
|
|
|
pm.last_payment_date,
|
|
|
|
|
apt.last_appointment_date
|
|
|
|
|
FROM "Patient" p
|
|
|
|
|
INNER JOIN ${pmSubquery} ON pm.patient_id = p.id
|
|
|
|
|
LEFT JOIN (
|
|
|
|
|
SELECT "patientId" AS patient_id, MAX("date") AS last_appointment_date
|
|
|
|
|
FROM "Appointment"
|
|
|
|
|
GROUP BY "patientId"
|
|
|
|
|
) apt ON apt.patient_id = p.id
|
|
|
|
|
WHERE COALESCE(pm.effective_balance, 0) = 0
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const orderBy = `ORDER BY pm.last_payment_date DESC NULLS LAST, p.id DESC`;
|
|
|
|
|
|
|
|
|
|
const query = `
|
|
|
|
|
${baseSelect}
|
|
|
|
|
${cursor ? keysetPredicate : ""}
|
|
|
|
|
${orderBy}
|
|
|
|
|
LIMIT ${safeLimit};
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const rows = (await prisma.$queryRawUnsafe(query)) as RawRow[];
|
|
|
|
|
|
|
|
|
|
let nextCursor: string | null = null;
|
|
|
|
|
if (rows.length === safeLimit) {
|
|
|
|
|
const last = rows[rows.length - 1];
|
|
|
|
|
if (last) {
|
|
|
|
|
nextCursor = encodeCursor({
|
|
|
|
|
lastPaymentDate: last.last_payment_date ? new Date(last.last_payment_date).toISOString() : null,
|
|
|
|
|
lastPatientId: Number(last.patient_id),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasMore = rows.length === safeLimit;
|
|
|
|
|
|
|
|
|
|
const balances: PatientBalanceRow[] = rows.map((r) => ({
|
|
|
|
|
patientId: Number(r.patient_id),
|
|
|
|
|
firstName: r.first_name,
|
|
|
|
|
lastName: r.last_name,
|
|
|
|
|
totalCharges: Number(r.total_charges ?? 0),
|
|
|
|
|
totalPayments: Number(r.total_paid ?? 0),
|
|
|
|
|
totalAdjusted: Number(r.total_adjusted ?? 0),
|
|
|
|
|
currentBalance: Number(r.current_balance ?? 0),
|
|
|
|
|
lastPaymentDate: r.last_payment_date ? new Date(r.last_payment_date).toISOString() : null,
|
|
|
|
|
lastAppointmentDate: r.last_appointment_date ? new Date(r.last_appointment_date).toISOString() : null,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const countAndTotalsSql = `
|
|
|
|
|
SELECT
|
|
|
|
|
COUNT(*)::int AS cnt,
|
|
|
|
|
COALESCE(SUM(t.total_charges),0)::numeric(14,2) AS total_charges,
|
|
|
|
|
COALESCE(SUM(t.total_paid),0)::numeric(14,2) AS total_collected
|
|
|
|
|
FROM (
|
|
|
|
|
SELECT
|
|
|
|
|
pay."patientId" AS patient_id,
|
|
|
|
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment")::numeric(14,2) AS total_paid,
|
|
|
|
|
SUM(
|
|
|
|
|
CASE WHEN pay.status = 'PAID' THEN 0
|
|
|
|
|
ELSE GREATEST(pay."totalBilled" - COALESCE(pay."mhPaidAmount",0) - pay."copayment", 0)
|
|
|
|
|
END
|
|
|
|
|
)::numeric(14,2) AS effective_balance
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
${paymentWhereClause}
|
|
|
|
|
GROUP BY pay."patientId"
|
|
|
|
|
) t
|
|
|
|
|
WHERE COALESCE(t.effective_balance, 0) = 0;
|
|
|
|
|
`;
|
|
|
|
|
const cntRows = (await prisma.$queryRawUnsafe(countAndTotalsSql)) as { cnt: number; total_charges: string; total_collected: string }[];
|
|
|
|
|
const totalCount = cntRows?.[0]?.cnt ?? 0;
|
|
|
|
|
const totalCharges = Number(cntRows?.[0]?.total_charges ?? 0);
|
|
|
|
|
const totalCollected = Number(cntRows?.[0]?.total_collected ?? 0);
|
|
|
|
|
|
|
|
|
|
return { balances, totalCount, nextCursor, hasMore, totalCharges, totalCollected };
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("[paymentsReportsStorage.getPatientsWithZeroBalance] error:", err);
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return just the paged balances for a doctor (same logic/filters as previous single-query approach)
|
|
|
|
|
*/
|
|
|
|
|
@@ -488,6 +646,11 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment")::numeric(14,2) AS total_paid,
|
|
|
|
|
SUM(pay."adjustment")::numeric(14,2) AS total_adjusted,
|
|
|
|
|
SUM(
|
|
|
|
|
CASE WHEN pay.status = 'PAID' THEN 0
|
|
|
|
|
ELSE GREATEST(pay."totalBilled" - COALESCE(pay."mhPaidAmount",0) - pay."copayment", 0)
|
|
|
|
|
END
|
|
|
|
|
)::numeric(14,2) AS effective_balance,
|
|
|
|
|
MAX(pay."createdAt") AS last_payment_date
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
JOIN "Claim" c ON pay."claimId" = c.id
|
|
|
|
|
@@ -510,7 +673,7 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
COALESCE(pa.total_charges, 0)::numeric(14,2) AS total_charges,
|
|
|
|
|
COALESCE(pa.total_paid, 0)::numeric(14,2) AS total_paid,
|
|
|
|
|
COALESCE(pa.total_adjusted, 0)::numeric(14,2) AS total_adjusted,
|
|
|
|
|
(COALESCE(pa.total_charges,0) - COALESCE(pa.total_paid,0) - COALESCE(pa.total_adjusted,0))::numeric(14,2) AS current_balance,
|
|
|
|
|
COALESCE(pa.effective_balance, 0)::numeric(14,2) AS current_balance,
|
|
|
|
|
pa.last_payment_date,
|
|
|
|
|
-- epoch milliseconds for last payment date (NULL when last_payment_date is NULL)
|
|
|
|
|
(CASE WHEN pa.last_payment_date IS NULL THEN NULL
|
|
|
|
|
@@ -678,10 +841,12 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
payments_agg AS (
|
|
|
|
|
SELECT
|
|
|
|
|
pay."patientId" AS patient_id,
|
|
|
|
|
SUM(pay."totalBilled")::numeric(14,2) AS total_charges,
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0))::numeric(14,2) AS mh_paid,
|
|
|
|
|
SUM(pay."copayment")::numeric(14,2) AS copayment,
|
|
|
|
|
SUM(pay."adjustment")::numeric(14,2) AS adjustment
|
|
|
|
|
SUM(COALESCE(pay."mhPaidAmount",0) + pay."copayment")::numeric(14,2) AS total_collected,
|
|
|
|
|
SUM(
|
|
|
|
|
CASE WHEN pay.status = 'PAID' THEN 0
|
|
|
|
|
ELSE GREATEST(pay."totalBilled" - COALESCE(pay."mhPaidAmount",0) - pay."copayment", 0)
|
|
|
|
|
END
|
|
|
|
|
)::numeric(14,2) AS effective_balance
|
|
|
|
|
FROM "Payment" pay
|
|
|
|
|
JOIN "Claim" c ON pay."claimId" = c.id
|
|
|
|
|
WHERE c."staffId" = ${Number(staffId)}
|
|
|
|
|
@@ -689,10 +854,10 @@ export const paymentsReportsStorage: IPaymentsReportsStorage = {
|
|
|
|
|
GROUP BY pay."patientId"
|
|
|
|
|
)
|
|
|
|
|
SELECT json_build_object(
|
|
|
|
|
'totalPatients', COALESCE(COUNT(DISTINCT pa.patient_id),0),
|
|
|
|
|
'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.mh_paid,0) + COALESCE(pa.copayment,0)),0)::text,
|
|
|
|
|
'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)
|
|
|
|
|
'totalPatients', COALESCE(COUNT(DISTINCT pa.patient_id),0),
|
|
|
|
|
'totalOutstanding', COALESCE(SUM(pa.effective_balance),0)::text,
|
|
|
|
|
'totalCollected', COALESCE(SUM(pa.total_collected),0)::text,
|
|
|
|
|
'patientsWithBalance',COALESCE(SUM(CASE WHEN pa.effective_balance > 0 THEN 1 ELSE 0 END),0)
|
|
|
|
|
) AS summary_json
|
|
|
|
|
FROM payments_agg pa;
|
|
|
|
|
`;
|
|
|
|
|
|