diff --git a/src/hooks/usePolicyForMovingExpenses.ts b/src/hooks/usePolicyForMovingExpenses.ts index be09beaa39f9..5de9524f14e8 100644 --- a/src/hooks/usePolicyForMovingExpenses.ts +++ b/src/hooks/usePolicyForMovingExpenses.ts @@ -8,6 +8,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -100,7 +101,7 @@ function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: // Contextual selector — captures login/flags from closure. // Returns only IDs + flags (stable output) to prevent re-renders when unrelated policies change. const policyQualificationSelector = (policies: OnyxCollection) => getPolicyQualificationResult(policies, login, isPerDiemRequest, isTimeRequest, expensePolicyID); - const [qualificationResult] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { + const [qualificationResult, policiesLoadStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { selector: policyQualificationSelector, }); @@ -110,17 +111,30 @@ function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: const resolvedPolicyID = validExpensePolicyID ?? singlePolicyID; const [resolvedPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${resolvedPolicyID}`); - // User has no eligible policy - if (!resolvedPolicyID) { - return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, shouldNavigateToUpgradePath: true}; - } + // Gate the upgrade path on policy hydration. Without this, during Onyx cold-start the collection reads + // empty and we'd report that a member of a group workspace has none, sending them to MONEY_REQUEST_UPGRADE. + // `useCreateReport` guards the same way. + const arePoliciesLoaded = !isLoadingOnyxValue(policiesLoadStatus); // If this is an employee's card transaction that we manage, then we should report it to their default policy // which we don't know. Sending an empty `policyID` instructs the backend to auto-select the preferred policy. + // This never depends on a locally resolved policy, so it has to be answered before the upgrade path below. if (isUnreportedManagedCardTransaction) { return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false}; } + // User has no eligible policy + if (!resolvedPolicyID) { + // The active workspace can still be a valid destination even when the qualification pass came back + // empty, so check it before giving up. This has to run ahead of the upgrade path below, otherwise a + // perfectly valid active workspace can never rescue the user. + if (isPolicyValidForMovingExpenses(activePolicy, login, isPerDiemRequest, isTimeRequest)) { + return {policyForMovingExpensesID: activePolicyID, policyForMovingExpenses: activePolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false}; + } + + return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, shouldNavigateToUpgradePath: arePoliciesLoaded}; + } + // If an expense policy ID is provided and valid, prefer it over the active policy if (validExpensePolicyID) { return {policyForMovingExpensesID: validExpensePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false}; diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index f32e4d4a8902..1f5d250eedf0 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -87,7 +87,9 @@ function EmptySearchView({similarSearchHash, type, hasResults, queryJSON, violat const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`); - const groupPoliciesWithChatEnabled = getGroupPoliciesWhereReportCanBeCreated(allPolicies); + // The login is required for the `employeeList` role fallback in `getPolicyRole`. Without it, every policy + // that only carries the member's role in `employeeList` is filtered out and the user looks workspace-less. + const groupPoliciesWithChatEnabled = getGroupPoliciesWhereReportCanBeCreated(allPolicies, currentUserPersonalDetails?.login); const [hasSeenTour = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { selector: hasSeenTourSelector,