diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2c2ec333429a7..7255825734b99 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11088,6 +11088,7 @@ function createDraftTransactionAndNavigateToParticipantSelector( introSelected: OnyxEntry, allTransactionDrafts: OnyxCollection, activePolicy: OnyxEntry, + userBillingGraceEndPeriodCollection: OnyxCollection, isRestrictedToPreferredPolicy = false, preferredPolicyID?: string, ): void { @@ -11153,7 +11154,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 22500592497fa..62b8bb37c621a 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); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); @@ -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, ); }, }); @@ -623,6 +627,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail activePolicy, parentReport, reportActionsForOriginalReportID, + userBillingGraceEndPeriodCollection, ]); const displayNamesWithTooltips = useMemo(() => { diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index 8d5787a729a7b..f8846b69992d1 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -403,6 +403,7 @@ type PureReportActionItemProps = { introSelected: OnyxEntry, allTransactionDrafts: OnyxCollection, activePolicy: OnyxEntry, + userBillingGraceEndPeriodCollection: OnyxCollection, isRestrictedToPreferredPolicy?: boolean, preferredPolicyID?: string, ) => void; @@ -482,6 +483,9 @@ type PureReportActionItemProps = { /** Report metadata for the report */ reportMetadata?: OnyxEntry; + + /** The billing grace end period's shared NVP collection */ + userBillingGraceEndPeriodCollection: OnyxCollection; }; // This is equivalent to returning a negative boolean in normal functions, but we can keep the element return type @@ -557,6 +561,7 @@ function PureReportActionItem({ reportNameValuePairsOrigin, reportNameValuePairsOriginalID, reportMetadata, + userBillingGraceEndPeriodCollection, }: PureReportActionItemProps) { const {transitionActionSheetState} = ActionSheetAwareScrollView.useActionSheetAwareScrollViewActions(); const {translate, formatPhoneNumber, localeCompare, formatTravelDate, getLocalDateFromDatetime, datetimeToCalendarTime} = useLocalize(); @@ -955,6 +960,7 @@ function PureReportActionItem({ introSelected, allTransactionDrafts, activePolicy, + undefined, isRestrictedToPreferredPolicy, preferredPolicyID, ); @@ -976,6 +982,7 @@ function PureReportActionItem({ introSelected, allTransactionDrafts, activePolicy, + userBillingGraceEndPeriodCollection, ); }, }, @@ -991,6 +998,7 @@ function PureReportActionItem({ introSelected, allTransactionDrafts, activePolicy, + undefined, ); }, }, @@ -1135,6 +1143,7 @@ function PureReportActionItem({ report, originalReport, personalPolicyID, + userBillingGraceEndPeriodCollection, ]); /** diff --git a/src/pages/inbox/report/ReportActionItem.tsx b/src/pages/inbox/report/ReportActionItem.tsx index c35e8513f2254..08b797f072bbf 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; @@ -110,6 +110,7 @@ function ReportActionItem({ const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); + const [userBillingGraceEndPeriodCollection] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const transactionsOnIOUReport = useReportTransactions(iouReport?.reportID); const transactionID = isMoneyRequestAction(action) && getOriginalMessage(action)?.IOUTransactionID; @@ -192,6 +193,7 @@ function ReportActionItem({ isTryNewDotNVPDismissed={isTryNewDotNVPDismissed} bankAccountList={bankAccountList} reportMetadata={reportMetadata} + userBillingGraceEndPeriodCollection={userBillingGraceEndPeriodCollection} /> ); } diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 073522df53765..82010f613074f 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/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx index f95e12b71dbea..2705d2e3375e9 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} /> @@ -319,6 +320,7 @@ describe('PureReportActionItem', () => { reportMetadata={reportMetadata} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -377,6 +379,7 @@ describe('PureReportActionItem', () => { iouReportOfLinkedReport={undefined} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -447,6 +450,7 @@ describe('PureReportActionItem', () => { iouReportOfLinkedReport={undefined} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -512,6 +516,7 @@ describe('PureReportActionItem', () => { iouReportOfLinkedReport={undefined} currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} + userBillingGraceEndPeriodCollection={undefined} /> @@ -564,6 +569,7 @@ describe('PureReportActionItem', () => { currentUserAccountID={ACTOR_ACCOUNT_ID} allTransactionDrafts={undefined} modifiedExpenseMessage={modifiedExpenseMessage} + userBillingGraceEndPeriodCollection={undefined} /> diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 4f287a8d154bd..298a9dbb35a4f 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -12311,7 +12311,7 @@ describe('ReportUtils', () => { await Onyx.set(ONYXKEYS.SESSION, {email: currentUserEmail, accountID: currentUserAccountID}); }); - it("should navigate to the restricted action page if the active policy's billable actions are restricted", async () => { + it("should navigate to the restricted action page if the policy owner's billable actions are restricted", async () => { // Given a transaction and an active policy where billable actions are restricted const transaction = createRandomTransaction(1); const activePolicy: Policy = { @@ -12326,7 +12326,41 @@ 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)); + }); + + it("should navigate to the restricted action page if the policy non-owner's billable actions are restricted", async () => { + // Given a transaction and an active policy where billable actions are restricted for non-owner + const transaction = createRandomTransaction(3); + const ownerAccountID = 123; + const activePolicy: Policy = { + ...createRandomPolicy(102), + ownerAccountID, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${1}`, {}); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${activePolicy.id}`, activePolicy); + const userBillingGraceEndPeriodCollection = { + [`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END}${ownerAccountID}`]: { + value: 1, + }, + }; + + // When we call createDraftTransactionAndNavigateToParticipantSelector + createDraftTransactionAndNavigateToParticipantSelector( + transaction.transactionID, + '1', + CONST.IOU.ACTION.CATEGORIZE, + '1', + undefined, + undefined, + activePolicy, + userBillingGraceEndPeriodCollection, + ); // Then it should navigate to the restricted action page expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.RESTRICTED_ACTION.getRoute(activePolicy.id)); @@ -12354,7 +12388,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( @@ -12384,7 +12418,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( @@ -12401,7 +12435,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( @@ -12439,7 +12473,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( @@ -12473,7 +12507,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');