diff --git a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx index faa3114c1..8cbd9e45e 100644 --- a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx +++ b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.spec.tsx @@ -177,6 +177,33 @@ describe('BillingAccountLineItemsModal', () => { .toBeNull() }) + it('uses billing-account markup for locked challenge fees when challenge markup cannot be loaded', () => { + mockedFetchChallenge.mockRejectedValueOnce(new Error('Forbidden')) + + renderModal({ + ...baseBillingAccountDetails, + lockedAmounts: [ + { + amount: '28.6', + date: '2026-05-12T00:00:00.000Z', + externalId: '5fdf48d2-811f-4914-b713-9e5f423c907d', + externalName: 'Copilot and Admin with reviews', + externalType: 'CHALLENGE', + }, + ], + lockedBudget: 28.6, + markup: 0.33, + totalBudgetRemaining: 971.4, + }) + + expect(screen.getAllByText('$28.60')) + .toHaveLength(2) + expect(screen.getByText('$9.44')) + .toBeTruthy() + expect(screen.queryByText('-')) + .toBeNull() + }) + it('removes challenge markup once from consumed challenge charges before showing member payments', async () => { renderModal({ ...baseBillingAccountDetails, diff --git a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx index 8849d0d81..3aecd2cde 100644 --- a/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx +++ b/src/apps/work/src/lib/components/BillingAccountLineItemsModal/BillingAccountLineItemsModal.tsx @@ -272,10 +272,11 @@ async function fetchChallengeDetailsById( * @param item Billing-account line item being displayed. * @param billingAccountDetails Billing account detail payload. * @param challengeDetailsById Hydrated challenge details, or `undefined` while loading. - * @returns Challenge-specific markup, billing-account fallback for legacy - * rows, or `undefined` when the challenge row is still being hydrated. + * @returns Challenge-specific markup when available, otherwise the billing-account markup fallback. * @remarks Canonical challenge rows use challenge billing markup so `0` markup - * challenges do not inherit the billing account default fee. + * challenges do not inherit the billing account default fee once hydrated. The + * billing-account fallback keeps locked rows from showing a blank fee when + * challenge hydration is unavailable. */ function getLineItemChallengeMarkup( item: BillingAccountLineItem, @@ -291,6 +292,7 @@ function getLineItemChallengeMarkup( } return challengeDetailsById?.get(challengeId)?.billing?.markup + ?? billingAccountDetails.markup } /**