diff --git a/src/lib/api.ts b/src/lib/api.ts index b7bd184..fdc3723 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -17,6 +17,8 @@ * - `health()` GETs `/api/health` and returns `{ status, version }`. * - `listBatches / getBatch / listClaims / listRemittances / listProviders * / listActivity` are plain JSON GETs against the persistence surface. + * - `listUnmatched / matchRemit / unmatchClaim` hit the reconciliation + * surface. POSTs throw `ApiError` so callers can branch on `.status`. */ import type { @@ -24,6 +26,7 @@ import type { ClaimPayment, Envelope, FinancialInfo, + MatchResponse, NdjsonEvent, ParseResult837, ParseResult835, @@ -31,6 +34,8 @@ import type { Payer835, Payee835, ReassociationTrace, + UnmatchedClaim, + UnmatchedResponse, BatchSummary as ParserBatchSummary, } from "@/types"; @@ -147,6 +152,16 @@ function notConfiguredError(): Error { ); } +/** + * Error thrown for non-2xx responses. Carries the HTTP `status` so callers can + * branch on it (e.g. 404 vs 500) without parsing the message string. + */ +export class ApiError extends Error { + constructor(public status: number, message: string) { + super(message); + } +} + function joinUrl(path: string): string { return `${BASE_URL.replace(/\/$/, "")}${path}`; } @@ -436,6 +451,61 @@ async function listActivity( return (await res.json()) as PaginatedResponse; } +// --------------------------------------------------------------------------- +// Public surface — reconciliation endpoints (sub-project 2) +// POSTs throw `ApiError` so callers can inspect `.status`; the GET is shaped +// like the other list endpoints above. +// --------------------------------------------------------------------------- + +async function listUnmatched(): Promise { + if (!isConfigured) throw notConfiguredError(); + const res = await fetch(joinUrl(`/api/reconciliation/unmatched`), { + headers: { Accept: "application/json" }, + }); + if (!res.ok) { + const detail = await readErrorBody(res); + throw new ApiError(res.status, detail || res.statusText); + } + return (await res.json()) as UnmatchedResponse; +} + +async function matchRemit( + claimId: string, + remitId: string +): Promise { + if (!isConfigured) throw notConfiguredError(); + const res = await fetch(joinUrl(`/api/reconciliation/match`), { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ claim_id: claimId, remit_id: remitId }), + }); + if (!res.ok) { + const detail = await readErrorBody(res); + throw new ApiError(res.status, detail || res.statusText); + } + return (await res.json()) as MatchResponse; +} + +async function unmatchClaim(claimId: string): Promise<{ claim: UnmatchedClaim }> { + if (!isConfigured) throw notConfiguredError(); + const res = await fetch(joinUrl(`/api/reconciliation/unmatch`), { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ claim_id: claimId }), + }); + if (!res.ok) { + const detail = await readErrorBody(res); + throw new ApiError(res.status, detail || res.statusText); + } + return (await res.json()) as { claim: UnmatchedClaim }; +} + export const api = { isConfigured, baseUrl: BASE_URL, @@ -448,4 +518,7 @@ export const api = { listRemittances, listProviders, listActivity, + listUnmatched, + matchRemit, + unmatchClaim, };