feat(frontend): add listUnmatched, matchRemit, unmatchClaim to api

This commit is contained in:
Tyler
2026-06-19 23:47:34 -06:00
parent d4a142aa83
commit 23ac7f6fda
+73
View File
@@ -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<T = unknown>(
return (await res.json()) as PaginatedResponse<T>;
}
// ---------------------------------------------------------------------------
// 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<UnmatchedResponse> {
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<MatchResponse> {
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,
};