feat: payment PDF extraction, import, and remittance tracking

- Add Upload Payment Documents section with Extract & Download (Excel)
  and Extract & Import (database) buttons
- PDF extractor (pdfplumber) parses MassHealth RA PDFs: two-pass
  strategy joins summary-page ICN/patient map with detail-page
  procedure data (CDT code, paid code, tooth, date, allowed amount)
- RA cover-page summary (Payee ID, RA #, Payment Amount, etc.)
  included as separate Excel sheet; numeric values written as numbers
- Backend PDF import route groups rows by Member #, finds/creates
  patient, creates Payment + ServiceLines with ICN per procedure
- Add icn, paidCode, allowedAmount fields to ServiceLine schema
- Payments table: status simplified to Paid in Full / Balance;
  adjustment auto-computed on mhPaidAmount/copayment change;
  Paid in Full and Revert buttons with confirmation dialogs
- Edit Payment modal: shows ICN, Paid Code, Allowed Amount per line
- PDF Import badge distinguishes from OCR imports in payments table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-07 12:53:50 -04:00
parent e204d30ff6
commit dd0df4a435
76 changed files with 1570 additions and 96 deletions

View File

@@ -504,6 +504,10 @@ export default function PaymentEditModal({
<span className="bg-gray-100 text-gray-800 px-2 py-0.5 rounded-full font-medium">
Claim #{payment.claimId.toString().padStart(4, "0")}
</span>
) : payment.notes?.startsWith("PDF import") ? (
<span className="bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full font-medium">
PDF Import
</span>
) : (
<span className="bg-gray-100 text-gray-800 px-2 py-0.5 rounded-full font-medium">
OCR Imported Payment
@@ -628,6 +632,28 @@ export default function PaymentEditModal({
{line.procedureCode}
</span>
</p>
{(line as any).icn && (
<p>
<span className="text-gray-500">ICN:</span>{" "}
<span className="font-medium font-mono text-xs">
{(line as any).icn}
</span>
</p>
)}
{(line as any).paidCode && (line as any).paidCode !== line.procedureCode && (
<p>
<span className="text-gray-500">Paid Code:</span>{" "}
<span className="font-medium">{(line as any).paidCode}</span>
</p>
)}
{(line as any).allowedAmount != null && (
<p>
<span className="text-gray-500">Allowed:</span>{" "}
<span className="font-medium text-blue-600">
${Number((line as any).allowedAmount).toFixed(2)}
</span>
</p>
)}
{(line as any).quad && (
<p>
<span className="text-gray-500">Quad:</span>{" "}