From 9a9bff990570c4a34cbcf2a3c05909156eca15bd Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Feb 2026 22:31:46 +0800 Subject: [PATCH 1/5] pass billing grace end period data from useOnyx --- src/libs/ReportUtils.ts | 3 ++- src/pages/ReportDetailsPage.tsx | 4 ++++ src/pages/inbox/report/PureReportActionItem.tsx | 8 ++++++++ src/pages/inbox/report/ReportActionItem.tsx | 2 ++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 56be27480e5b2..93af3acb93574 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11083,6 +11083,7 @@ function createDraftTransactionAndNavigateToParticipantSelector( introSelected: OnyxEntry, allTransactionDrafts: OnyxCollection, activePolicy: OnyxEntry, + userBillingGraceEndPeriodCollection: OnyxCollection, isRestrictedToPreferredPolicy = false, preferredPolicyID?: string, ): void { @@ -11148,7 +11149,7 @@ function createDraftTransactionAndNavigateToParticipantSelector( } if (actionName === CONST.IOU.ACTION.CATEGORIZE) { - if (activePolicy && shouldRestrictUserBillableActions(activePolicy.id)) { + if (activePolicy && shouldRestrictUserBillableActions(activePolicy.id, userBillingGraceEndPeriodCollection)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(activePolicy.id)); return; } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index ce32a79fcb54f..9fc8438b05ae7 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -164,6 +164,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail const expensifyIcons = useMemoizedLazyExpensifyIcons(['Users', 'Gear', 'Send', 'Folder', 'UserPlus', 'Pencil', 'Checkmark', 'Building', 'Exit', 'Bug', 'Camera', 'Trashcan']); const backTo = route.params.backTo; + const [userBillingGraceEndPeriodCollection] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END, {canBeMissing: true}); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, {canBeMissing: true}); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`, {canBeMissing: true}); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); @@ -458,6 +459,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail introSelected, allTransactionDrafts, activePolicy, + undefined, isRestrictedToPreferredPolicy, preferredPolicyID, ); @@ -479,6 +481,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail introSelected, allTransactionDrafts, activePolicy, + userBillingGraceEndPeriodCollection ); }, }); @@ -497,6 +500,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail introSelected, allTransactionDrafts, activePolicy, + undefined, ); }, }); diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index 260bb9ece4801..e153093dae199 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -400,6 +400,7 @@ type PureReportActionItemProps = { introSelected: OnyxEntry, allTransactionDrafts: OnyxCollection, activePolicy: OnyxEntry, + userBillingGraceEndPeriodCollection: OnyxCollection, isRestrictedToPreferredPolicy?: boolean, preferredPolicyID?: string, ) => void; @@ -473,6 +474,8 @@ type PureReportActionItemProps = { /** Report metadata for the report */ reportMetadata?: OnyxEntry; + + userBillingGraceEndPeriodCollection: OnyxCollection; }; // This is equivalent to returning a negative boolean in normal functions, but we can keep the element return type @@ -548,6 +551,7 @@ function PureReportActionItem({ reportNameValuePairsOrigin, reportNameValuePairsOriginalID, reportMetadata, + userBillingGraceEndPeriodCollection, }: PureReportActionItemProps) { const {transitionActionSheetState} = ActionSheetAwareScrollView.useActionSheetAwareScrollViewActions(); const {translate, formatPhoneNumber, localeCompare, formatTravelDate, getLocalDateFromDatetime, datetimeToCalendarTime} = useLocalize(); @@ -946,6 +950,7 @@ function PureReportActionItem({ introSelected, allTransactionDrafts, activePolicy, + undefined, isRestrictedToPreferredPolicy, preferredPolicyID, ); @@ -967,6 +972,7 @@ function PureReportActionItem({ introSelected, allTransactionDrafts, activePolicy, + userBillingGraceEndPeriodCollection, ); }, }, @@ -982,6 +988,7 @@ function PureReportActionItem({ introSelected, allTransactionDrafts, activePolicy, + undefined, ); }, }, @@ -1126,6 +1133,7 @@ function PureReportActionItem({ report, originalReport, personalPolicyID, + userBillingGraceEndPeriodCollection, ]); /** diff --git a/src/pages/inbox/report/ReportActionItem.tsx b/src/pages/inbox/report/ReportActionItem.tsx index a1c6f6e394028..194310d1ae278 100644 --- a/src/pages/inbox/report/ReportActionItem.tsx +++ b/src/pages/inbox/report/ReportActionItem.tsx @@ -117,6 +117,7 @@ function ReportActionItem({ const [cardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true}); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true}); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); + const [userBillingGraceEndPeriodCollection] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END, {canBeMissing: true}); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || undefined}`]; @@ -187,6 +188,7 @@ function ReportActionItem({ isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} bankAccountList={bankAccountList} reportMetadata={reportMetadata} + userBillingGraceEndPeriodCollection={userBillingGraceEndPeriodCollection} /> ); } From 7bce33c3cc005e7b8a0c73df34ce1d1e8e2ca0a1 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Feb 2026 22:34:57 +0800 Subject: [PATCH 2/5] prettier --- src/pages/ReportDetailsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 9fc8438b05ae7..912f477010a31 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -481,7 +481,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail introSelected, allTransactionDrafts, activePolicy, - userBillingGraceEndPeriodCollection + userBillingGraceEndPeriodCollection, ); }, }); From 6e0ea32ace959285a3df56810992ee9a0e552aaa Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Feb 2026 22:56:57 +0800 Subject: [PATCH 3/5] lint --- src/pages/inbox/report/ReportActionItem.tsx | 2 +- tests/actions/IOUTest.ts | 6 ++++++ tests/unit/ReportUtilsTest.ts | 12 ++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/pages/inbox/report/ReportActionItem.tsx b/src/pages/inbox/report/ReportActionItem.tsx index 194310d1ae278..6fa92881fce7b 100644 --- a/src/pages/inbox/report/ReportActionItem.tsx +++ b/src/pages/inbox/report/ReportActionItem.tsx @@ -38,7 +38,7 @@ import PureReportActionItem from './PureReportActionItem'; type ReportActionItemProps = Omit< PureReportActionItemProps, - 'taskReport' | 'linkedReport' | 'iouReportOfLinkedReport' | 'currentUserAccountID' | 'personalPolicyID' | 'allTransactionDrafts' + 'taskReport' | 'linkedReport' | 'iouReportOfLinkedReport' | 'currentUserAccountID' | 'personalPolicyID' | 'allTransactionDrafts' | 'userBillingGraceEndPeriodCollection' > & { /** All the data of the report collection */ allReports: OnyxCollection; diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 0a3565e655546..e627a072b99d0 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -590,6 +590,7 @@ describe('actions/IOU', () => { {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, {}, undefined, + undefined, ); await waitForBatchedUpdates(); @@ -1149,6 +1150,7 @@ describe('actions/IOU', () => { {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, {}, undefined, + undefined, ); await waitForBatchedUpdates(); @@ -1559,6 +1561,7 @@ describe('actions/IOU', () => { {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, allTransactionDrafts, undefined, + undefined, ); await waitForBatchedUpdates(); @@ -1605,6 +1608,7 @@ describe('actions/IOU', () => { {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, {}, undefined, + undefined, ); await waitForBatchedUpdates(); @@ -1640,6 +1644,7 @@ describe('actions/IOU', () => { {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, {}, undefined, + undefined, ); await waitForBatchedUpdates(); @@ -1670,6 +1675,7 @@ describe('actions/IOU', () => { {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, {}, undefined, + undefined, ); await waitForBatchedUpdates(); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 43073d01d9cb9..fbbab3c45f9b7 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -12136,7 +12136,7 @@ describe('ReportUtils', () => { await Onyx.merge(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END, Math.floor(Date.now() / 1000) - 3600); // When we call createDraftTransactionAndNavigateToParticipantSelector with the restricted policy - createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, activePolicy); + createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, activePolicy, undefined); // Then it should navigate to the restricted action page expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.RESTRICTED_ACTION.getRoute(activePolicy.id)); @@ -12164,7 +12164,7 @@ describe('ReportUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseReport.reportID}`, policyExpenseReport); // When we call createDraftTransactionAndNavigateToParticipantSelector - createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, activePolicy); + createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, activePolicy, undefined); // Then it should navigate to the category step expect(Navigation.navigate).toHaveBeenCalledWith( @@ -12194,7 +12194,7 @@ describe('ReportUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseReport.reportID}`, policyExpenseReport); // When we call createDraftTransactionAndNavigateToParticipantSelector with undefined activePolicy - createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '2', CONST.IOU.ACTION.CATEGORIZE, '2', undefined, undefined, undefined); + createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '2', CONST.IOU.ACTION.CATEGORIZE, '2', undefined, undefined, undefined, undefined); // Then it should automatically pick the available policy and navigate to the category step expect(Navigation.navigate).toHaveBeenCalledWith( @@ -12211,7 +12211,7 @@ describe('ReportUtils', () => { await Onyx.setCollection(ONYXKEYS.COLLECTION.POLICY, {}); // When we call createDraftTransactionAndNavigateToParticipantSelector with undefined activePolicy - createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, undefined); + createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, undefined, undefined); // Then it should navigate to the upgrade page because no policies were found to categorize with expect(Navigation.navigate).toHaveBeenCalledWith( @@ -12249,7 +12249,7 @@ describe('ReportUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy2.id}`, policy2); // When we call createDraftTransactionAndNavigateToParticipantSelector with undefined activePolicy - createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, undefined); + createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, undefined, undefined); // Then it should navigate to the upgrade page because it's ambiguous which policy to use expect(Navigation.navigate).toHaveBeenCalledWith( @@ -12283,7 +12283,7 @@ describe('ReportUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${activePolicy.id}`, activePolicy); // When we call createDraftTransactionAndNavigateToParticipantSelector - createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, activePolicy); + createDraftTransactionAndNavigateToParticipantSelector(transaction.transactionID, '1', CONST.IOU.ACTION.CATEGORIZE, '1', undefined, undefined, activePolicy, undefined); // Then it should log a warning and not navigate expect(logWarnSpy).toHaveBeenCalledWith('policyExpenseReportID is not valid during expense categorizing'); From 2add411e13ca22b16df1d35e421d2dd5e3e5a836 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Feb 2026 23:12:54 +0800 Subject: [PATCH 4/5] lint --- src/pages/ReportDetailsPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 912f477010a31..89b9f0e5eeb06 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -627,6 +627,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail activePolicy, parentReport, reportActionsForOriginalReportID, + userBillingGraceEndPeriodCollection, ]); const displayNamesWithTooltips = useMemo(() => { From 9c8be20ebad7623e9c2c99f27902e25877f96a60 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 20 Feb 2026 23:17:24 +0800 Subject: [PATCH 5/5] missing param --- tests/ui/PureReportActionItemTest.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index e9e6cf986ce7e..be9287b8055fa 100644 --- a/tests/ui/PureReportActionItemTest.tsx +++ b/tests/ui/PureReportActionItemTest.tsx @@ -112,6 +112,7 @@ describe('PureReportActionItem', () => { iouReportOfLinkedReport={undefined} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -292,6 +293,7 @@ describe('PureReportActionItem', () => { reportMetadata={reportMetadata} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -350,6 +352,7 @@ describe('PureReportActionItem', () => { iouReportOfLinkedReport={undefined} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -420,6 +423,7 @@ describe('PureReportActionItem', () => { iouReportOfLinkedReport={undefined} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -485,6 +489,7 @@ describe('PureReportActionItem', () => { iouReportOfLinkedReport={undefined} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -537,6 +542,7 @@ describe('PureReportActionItem', () => { currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} modifiedExpenseMessage={modifiedExpenseMessage} + userBillingGraceEndPeriodCollection={undefined} />