feat(ocr) - route fixed, saving done
This commit is contained in:
@@ -156,41 +156,56 @@ export default function PaymentOCRBlock() {
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const payload = rows.map((row) => {
|
||||
const billed = Number(row["Billed Amount"] ?? 0);
|
||||
const allowed = Number(row["Allowed Amount"] ?? 0);
|
||||
const paid = Number(row["Paid Amount"] ?? 0);
|
||||
const skipped: string[] = [];
|
||||
|
||||
return {
|
||||
patientId: parseInt(row["Patient ID"] as string, 10),
|
||||
totalBilled: billed,
|
||||
totalPaid: paid,
|
||||
totalAdjusted: billed - allowed, // ❗ write-off
|
||||
totalDue: allowed - paid, // ❗ patient responsibility
|
||||
notes: `OCR import - CDT ${row["CDT Code"]}, Tooth ${row["Tooth"]}, Date ${row["Date SVC"]}`,
|
||||
serviceLine: {
|
||||
const payload = rows
|
||||
.map((row, idx) => {
|
||||
const patientName = row["Patient Name"];
|
||||
const patientId = row["Patient ID"];
|
||||
const procedureCode = row["CDT Code"];
|
||||
|
||||
if (!patientName || !patientId || !procedureCode) {
|
||||
skipped.push(`Row ${idx + 1} (missing name/id/procedureCode)`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
patientName,
|
||||
insuranceId: patientId,
|
||||
icn: row["ICN"] ?? null,
|
||||
procedureCode: row["CDT Code"],
|
||||
procedureDate: convertOCRDate(row["Date SVC"]), // you’ll parse "070825" → proper Date
|
||||
toothNumber: row["Tooth"],
|
||||
totalBilled: billed,
|
||||
totalPaid: paid,
|
||||
totalAdjusted: billed - allowed,
|
||||
totalDue: allowed - paid,
|
||||
},
|
||||
transaction: {
|
||||
paidAmount: paid,
|
||||
adjustedAmount: billed - allowed, // same as totalAdjusted
|
||||
method: "OTHER", // fallback, since OCR doesn’t give this
|
||||
receivedDate: new Date(),
|
||||
},
|
||||
};
|
||||
});
|
||||
toothNumber: row["Tooth"] ?? null,
|
||||
toothSurface: row["Surface"] ?? null,
|
||||
procedureDate: row["Date SVC"] ?? null,
|
||||
totalBilled: Number(row["Billed Amount"] ?? 0),
|
||||
totalAllowed: Number(row["Allowed Amount"] ?? 0),
|
||||
totalPaid: Number(row["Paid Amount"] ?? 0),
|
||||
sourceFile: row["Source File"] ?? null,
|
||||
};
|
||||
})
|
||||
.filter((r) => r !== null);
|
||||
|
||||
const res = await apiRequest(
|
||||
"POST",
|
||||
"/api/payments/ocr",
|
||||
JSON.stringify({ payments: payload })
|
||||
);
|
||||
if (skipped.length > 0) {
|
||||
toast({
|
||||
title:
|
||||
"Some rows skipped, because of either no patient Name or MemberId given.",
|
||||
description: skipped.join(", "),
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
|
||||
if (payload.length === 0) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "No valid rows to save",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await apiRequest("POST", "/api/payments/full-ocr-import", {
|
||||
rows: payload,
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error("Failed to save OCR payments");
|
||||
|
||||
|
||||
@@ -370,6 +370,11 @@ export default function PaymentsRecentTable({
|
||||
paymentsData?.totalCount || 0
|
||||
);
|
||||
|
||||
const getName = (p: PaymentWithExtras) =>
|
||||
p.patient
|
||||
? `${p.patient.firstName} ${p.patient.lastName}`.trim()
|
||||
: (p.patientName ?? "Unknown");
|
||||
|
||||
const getInitials = (fullName: string) => {
|
||||
const parts = fullName.trim().split(/\s+/);
|
||||
const filteredParts = parts.filter((part) => part.length > 0);
|
||||
@@ -480,7 +485,7 @@ export default function PaymentsRecentTable({
|
||||
<TableHead>Claim ID</TableHead>
|
||||
<TableHead>Patient Name</TableHead>
|
||||
<TableHead>Amount</TableHead>
|
||||
<TableHead>Claim Submitted on</TableHead>
|
||||
<TableHead>Service Date</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
@@ -519,6 +524,14 @@ export default function PaymentsRecentTable({
|
||||
const totalPaid = Number(payment.totalPaid || 0);
|
||||
const totalDue = Number(payment.totalDue || 0);
|
||||
|
||||
const displayName = getName(payment);
|
||||
const submittedOn =
|
||||
payment.serviceLines?.[0]?.procedureDate ??
|
||||
payment.claim?.createdAt ??
|
||||
payment.createdAt ??
|
||||
payment.serviceLineTransactions?.[0]?.receivedDate ??
|
||||
null;
|
||||
|
||||
return (
|
||||
<TableRow key={payment.id}>
|
||||
{allowCheckbox && (
|
||||
@@ -547,13 +560,13 @@ export default function PaymentsRecentTable({
|
||||
className={`h-10 w-10 ${getAvatarColor(Number(payment.id))}`}
|
||||
>
|
||||
<AvatarFallback className="text-white">
|
||||
{getInitials(payment.patientName)}
|
||||
{getInitials(displayName)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="ml-4">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{payment.patientName}
|
||||
{displayName}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
PID-{payment.patientId?.toString().padStart(4, "0")}
|
||||
@@ -585,7 +598,7 @@ export default function PaymentsRecentTable({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{formatDateToHumanReadable(payment.paymentDate)}
|
||||
{formatDateToHumanReadable(submittedOn)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
|
||||
Reference in New Issue
Block a user