feat(sp29): inbox rejected-row 999 ack chips + per-row Resubmit
- backend: _ack_summary_for_claims helper attaches claim_acks payload to inbox rejected-lane rows (3 tests) - frontend: InboxRow renders newest 3 AK2 chips + '+N more' overflow under the rejection reason, with '999 not linked' marker when an ack couldn't be linked to a claim - frontend: per-row Resubmit button downloads a single corrected 837 via api.serializeClaim837 (no bulk modal, no zip — one click, one .x12 file) - frontend: 2 new tests for the chip rendering and the per-row download flow
This commit is contained in:
@@ -87,4 +87,115 @@ describe("InboxRow", () => {
|
||||
);
|
||||
expect(container.textContent).toContain("92");
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// SP29: per-AK2 chip evidence + per-row Resubmit gesture for the
|
||||
// rejected-lane drill. The page renders 999 AK2 set-response codes
|
||||
// inline on the row so the operator can spot "this batch had R/E/X
|
||||
// rejects" without opening the drawer. The per-row Resubmit button
|
||||
// downloads a single corrected 837.
|
||||
// ---------------------------------------------------------------------
|
||||
it("SP29: rejected row renders inline 999 ack evidence chips + Resubmit button", () => {
|
||||
// 4 linked 999 AK2 set-responses — 3 will render as chips, the
|
||||
// 4th triggers the "+N more" overflow indicator.
|
||||
const rejectedRow: InboxClaimRow = {
|
||||
...baseClaim,
|
||||
id: "REJ1",
|
||||
claim_acks: {
|
||||
total: 4,
|
||||
rejected: 2,
|
||||
items: [
|
||||
{
|
||||
ack_id: 11,
|
||||
set_control_number: "991102989",
|
||||
set_accept_reject_code: "A",
|
||||
ak2_index: 0,
|
||||
linked_at: "2026-07-02T10:03:00Z",
|
||||
},
|
||||
{
|
||||
ack_id: 12,
|
||||
set_control_number: "991102989",
|
||||
set_accept_reject_code: "R",
|
||||
ak2_index: 1,
|
||||
linked_at: "2026-07-02T10:02:00Z",
|
||||
},
|
||||
{
|
||||
ack_id: 13,
|
||||
set_control_number: "991102989",
|
||||
set_accept_reject_code: "E",
|
||||
ak2_index: 2,
|
||||
linked_at: "2026-07-02T10:01:00Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const onResubmitOne = vi.fn();
|
||||
const { container } = render(
|
||||
<table>
|
||||
<tbody>
|
||||
<InboxRow
|
||||
row={rejectedRow}
|
||||
accent="oxblood"
|
||||
onClick={() => {}}
|
||||
onResubmitOne={onResubmitOne}
|
||||
/>
|
||||
</tbody>
|
||||
</table>,
|
||||
);
|
||||
|
||||
// Chip visible — the operator can read at a glance that the most
|
||||
// recent AK2 from this batch was an Accept, then a Reject, then
|
||||
// an Error. The "999 · A" / "999 · R" / "999 · E" text comes from
|
||||
// <AckCodeChip> rendering "<stcn>·<code>".
|
||||
expect(container.textContent).toContain("991102989·A");
|
||||
expect(container.textContent).toContain("991102989·R");
|
||||
expect(container.textContent).toContain("991102989·E");
|
||||
|
||||
// Overflow "+1 more" — total=4 but only 3 chips fit.
|
||||
expect(container.textContent).toContain("+1 more");
|
||||
|
||||
// Resubmit button rendered with the per-row testid.
|
||||
const btn = screen.getByTestId("resubmit-REJ1") as HTMLButtonElement;
|
||||
expect(btn).toBeTruthy();
|
||||
expect(btn.textContent).toBe("Resubmit");
|
||||
|
||||
// Click fires the per-row handler with the claim id; click does
|
||||
// NOT bubble to onClick (the row-onClick is for drilldown, the
|
||||
// button is a separate download gesture).
|
||||
fireEvent.click(btn);
|
||||
expect(onResubmitOne).toHaveBeenCalledTimes(1);
|
||||
expect(onResubmitOne).toHaveBeenCalledWith("REJ1");
|
||||
});
|
||||
|
||||
it("SP29: rejected row with no linked 999 acks shows the '999 not linked' orphan marker", () => {
|
||||
// A rejected claim whose 999 ack didn't match any ST02 in the
|
||||
// batch — the operator needs to know the drill lacked ack
|
||||
// evidence. The "999 not linked" italic line is the marker.
|
||||
const rejectedRow: InboxClaimRow = {
|
||||
...baseClaim,
|
||||
id: "REJ-ORPH",
|
||||
claim_acks: {
|
||||
total: 0,
|
||||
rejected: 0,
|
||||
items: [],
|
||||
},
|
||||
};
|
||||
const onResubmitOne = vi.fn();
|
||||
const { container } = render(
|
||||
<table>
|
||||
<tbody>
|
||||
<InboxRow
|
||||
row={rejectedRow}
|
||||
accent="oxblood"
|
||||
onClick={() => {}}
|
||||
onResubmitOne={onResubmitOne}
|
||||
/>
|
||||
</tbody>
|
||||
</table>,
|
||||
);
|
||||
expect(container.textContent).toContain("999 not linked");
|
||||
// The Resubmit button still renders so the operator can fix the
|
||||
// claim without first manually linking the orphan.
|
||||
expect(screen.getByTestId("resubmit-REJ-ORPH")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user