diff --git a/src/hooks/useParticipantSubmission.ts b/src/hooks/useParticipantSubmission.ts index eb8bc7cd3b14..6ccf5ee33c89 100644 --- a/src/hooks/useParticipantSubmission.ts +++ b/src/hooks/useParticipantSubmission.ts @@ -320,12 +320,13 @@ function useParticipantSubmission({ if ((isCategorizing || isShareAction) && numberOfParticipants.current === 0) { const email = userDetails.email ?? ''; const lastWorkspaceNumber = lastWorkspaceNumberSelector(policies, email); - const {expenseChatReportID, policyID, policyName} = createDraftWorkspace( - intro, - generateDefaultWorkspaceName(email, lastWorkspaceNumber, translate), - userDetails.accountID, - email, - ); + const {expenseChatReportID, policyID, policyName} = createDraftWorkspace({ + introSelected: intro, + workspaceName: generateDefaultWorkspaceName(email, lastWorkspaceNumber, translate), + currentUserAccountID: userDetails.accountID, + currentUserEmail: email, + currency: userDetails.localCurrencyCode ?? CONST.CURRENCY.USD, + }); for (const transaction of drafts) { setMoneyRequestParticipants(transaction.transactionID, [ { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5d1e8066585e..cbf9297f3eed 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11258,9 +11258,16 @@ function createDraftWorkspaceAndNavigateToConfirmationScreen( workspaceName: string, currentUserAccountID: number, currentUserEmail: string, + currentUserLocalCurrency: string, ): void { const isCategorizing = actionName === CONST.IOU.ACTION.CATEGORIZE; - const {expenseChatReportID, policyID, policyName} = createDraftWorkspace(introSelected, workspaceName, currentUserAccountID, currentUserEmail); + const {expenseChatReportID, policyID, policyName} = createDraftWorkspace({ + introSelected, + workspaceName, + currentUserAccountID, + currentUserEmail, + currency: currentUserLocalCurrency, + }); setMoneyRequestParticipants(transactionID, [ { selected: true, @@ -11294,6 +11301,7 @@ type CreateDraftTransactionParams = { transaction: OnyxEntry; currentUserAccountID: number; currentUserEmail: string; + currentUserLocalCurrency: string; }; function createDraftTransactionAndNavigateToParticipantSelector({ @@ -11311,6 +11319,7 @@ function createDraftTransactionAndNavigateToParticipantSelector({ transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency, }: CreateDraftTransactionParams): void { const transactionID = transaction?.transactionID; if (!transactionID || !reportID) { @@ -11455,7 +11464,7 @@ function createDraftTransactionAndNavigateToParticipantSelector({ return; } - return createDraftWorkspaceAndNavigateToConfirmationScreen(introSelected, transactionID, actionName, '', currentUserAccountID, currentUserEmail); + return createDraftWorkspaceAndNavigateToConfirmationScreen(introSelected, transactionID, actionName, '', currentUserAccountID, currentUserEmail, currentUserLocalCurrency); } /** diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index add5f88fa3e8..54a8e030e39a 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -3067,17 +3067,29 @@ function createWorkspace(options: CreateWorkspaceDataOptions): CreateWorkspacePa /** * Creates a draft workspace for various money request flows */ -function createDraftWorkspace( - introSelected: OnyxEntry, - workspaceName: string, - currentUserAccountID: number, - currentUserEmail: string, +type CreateDraftWorkspaceParams = { + introSelected: OnyxEntry; + workspaceName: string; + currentUserAccountID: number; + currentUserEmail: string; + policyOwnerEmail?: string; + makeMeAdmin?: boolean; + policyID?: string; + currency?: string; + file?: File; +}; + +function createDraftWorkspace({ + introSelected, + workspaceName, + currentUserAccountID, + currentUserEmail, policyOwnerEmail = '', makeMeAdmin = false, policyID = generatePolicyID(), currency = '', - file?: File, -): CreateWorkspaceParams { + file, +}: CreateDraftWorkspaceParams): CreateWorkspaceParams { const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(currency); const {expenseChatData, adminsChatReportID, adminsCreatedReportActionID, expenseChatReportID, expenseCreatedReportActionID} = ReportUtils.buildOptimisticWorkspaceChats( @@ -3234,7 +3246,7 @@ function buildDuplicatePolicyData(policy: Policy, options: DuplicatePolicyDataOp const isTravelOptionSelected = parts?.travel; const isCodingRulesOptionSelected = parts?.codingRules; - const outputCurrency = isOverviewOptionSelected ? policy?.outputCurrency : localCurrency; + const outputCurrency = isOverviewOptionSelected && policy?.outputCurrency ? policy?.outputCurrency : localCurrency; const policyMemberAccountIDs = isMemberOptionSelected ? Object.values(getMemberAccountIDsForWorkspace(policy?.employeeList, false, false)) : []; const {customUnitID: distanceCustomUnitID, customUnitRateID} = buildOptimisticDistanceRateCustomUnits(outputCurrency); @@ -4061,7 +4073,8 @@ function createWorkspaceFromIOUPayment( const policyID = generatePolicyID(); const workspaceName = generateDefaultWorkspaceName(currentUserEmail, lastWorkspaceNumber, localeTranslate); const employeeAccountID = iouReport?.ownerAccountID; - const {customUnits, customUnitID, customUnitRateID} = buildOptimisticDistanceRateCustomUnits(iouReport?.currency ?? currentUserLocalCurrency); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as iouReport?.currency could be an empty string + const {customUnits, customUnitID, customUnitRateID} = buildOptimisticDistanceRateCustomUnits(iouReport?.currency || currentUserLocalCurrency); const oldPersonalPolicyID = iouReport?.policyID; const iouReportID = iouReport?.reportID; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index eca440a146c1..fc5ffa8627b3 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -454,6 +454,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading const actionReportID = getOriginalReportID(report.reportID, parentReportAction, reportActionsForOriginalReportID); const whisperAction = getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); const actionableWhisperReportActionID = whisperAction?.reportActionID; + const currentUserLocalCurrency = currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD; items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.TRACK.SUBMIT, translationKey: 'actionableMentionTrackExpense.submit', @@ -476,6 +477,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading transaction: iouTransaction, currentUserAccountID: currentUserPersonalDetails.accountID, currentUserEmail: currentUserPersonalDetails.email ?? '', + currentUserLocalCurrency, }); }, }); @@ -500,6 +502,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading transaction: iouTransaction, currentUserAccountID: currentUserPersonalDetails.accountID, currentUserEmail: currentUserPersonalDetails.email ?? '', + currentUserLocalCurrency, }); }, }); @@ -523,6 +526,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading transaction: iouTransaction, currentUserAccountID: currentUserPersonalDetails.accountID, currentUserEmail: currentUserPersonalDetails.email ?? '', + currentUserLocalCurrency, }); }, }); @@ -638,6 +642,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata, reportLoading moneyRequestReport?.reportID, currentUserPersonalDetails.accountID, currentUserPersonalDetails.email, + currentUserPersonalDetails.localCurrencyCode, isTaskActionable, isRootGroupChat, leaveChat, diff --git a/src/pages/Travel/WorkspaceConfirmationForTravelPage.tsx b/src/pages/Travel/WorkspaceConfirmationForTravelPage.tsx index d463b75d1a40..cb18c7b3d9a1 100644 --- a/src/pages/Travel/WorkspaceConfirmationForTravelPage.tsx +++ b/src/pages/Travel/WorkspaceConfirmationForTravelPage.tsx @@ -10,6 +10,7 @@ import useOnyx from '@hooks/useOnyx'; import {createDraftWorkspace, createWorkspace} from '@libs/actions/Policy/Policy'; import Navigation from '@libs/Navigation/Navigation'; import type {TravelNavigatorParamList} from '@libs/Navigation/types'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -30,17 +31,15 @@ function WorkspaceConfirmationForTravelPage({route}: WorkspaceConfirmationForTra }; const onSubmit = (params: WorkspaceConfirmationSubmitFunctionParams) => { - createDraftWorkspace( + createDraftWorkspace({ introSelected, - params.name, - currentUserPersonalDetails.accountID, - currentUserPersonalDetails.email ?? '', - '', - false, - params.policyID, - params.currency, - params.avatarFile as File, - ); + workspaceName: params.name, + currentUserAccountID: currentUserPersonalDetails.accountID, + currentUserEmail: currentUserPersonalDetails.email ?? '', + policyID: params.policyID, + currency: params.currency || (currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD), + file: params.avatarFile as File, + }); createWorkspace({ policyName: params.name, policyID: params.policyID, diff --git a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx index de9e35235350..979b884a7b97 100644 --- a/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx +++ b/src/pages/inbox/report/actionContents/ChatActionableButtons.tsx @@ -171,6 +171,7 @@ function ChatActionableButtons({action, report, originalReport, reportID, origin transaction: trackExpenseTransaction, currentUserAccountID: personalDetail.accountID, currentUserEmail: personalDetail.email ?? '', + currentUserLocalCurrency: personalDetail.localCurrencyCode ?? CONST.CURRENCY.USD, }; const TRACK_EXPENSE_ACTIONS = { submit: CONST.IOU.ACTION.SUBMIT, diff --git a/src/pages/iou/request/step/IOURequestStepAccountant.tsx b/src/pages/iou/request/step/IOURequestStepAccountant.tsx index f5a1d223bf8e..23ae795c680c 100644 --- a/src/pages/iou/request/step/IOURequestStepAccountant.tsx +++ b/src/pages/iou/request/step/IOURequestStepAccountant.tsx @@ -9,6 +9,7 @@ import {generateDefaultWorkspaceName} from '@libs/actions/Policy/Policy'; import Navigation from '@libs/Navigation/Navigation'; import {createDraftWorkspaceAndNavigateToConfirmationScreen} from '@libs/ReportUtils'; import MoneyRequestAccountantSelector from '@pages/iou/request/MoneyRequestAccountantSelector'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -26,11 +27,9 @@ function IOURequestStepAccountant({ }, }: IOURequestStepAccountantProps) { const {translate} = useLocalize(); - const {accountID, login, email = ''} = useCurrentUserPersonalDetails(); - + const {accountID, login, email = '', localCurrencyCode} = useCurrentUserPersonalDetails(); const selector = (policies: OnyxCollection) => activeAdminPoliciesSelector(policies, login ?? ''); const lastWorkspaceNumberWithEmailSelector = (policies: OnyxCollection) => lastWorkspaceNumberSelector(policies, email); - const [adminPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector}); const [lastWorkspaceNumber] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: lastWorkspaceNumberWithEmailSelector}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); @@ -43,7 +42,15 @@ function IOURequestStepAccountant({ // Sharing with an accountant involves inviting them to the workspace and that requires admin access. const hasActiveAdminWorkspaces = (adminPolicies?.length ?? 0) > 0; if (!hasActiveAdminWorkspaces) { - createDraftWorkspaceAndNavigateToConfirmationScreen(introSelected, transactionID, action, generateDefaultWorkspaceName(email, lastWorkspaceNumber, translate), accountID, email); + createDraftWorkspaceAndNavigateToConfirmationScreen( + introSelected, + transactionID, + action, + generateDefaultWorkspaceName(email, lastWorkspaceNumber, translate), + accountID, + email, + localCurrencyCode ?? CONST.CURRENCY.USD, + ); return; } diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index f2eb1142f3d6..79ae8d23d49f 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -484,6 +484,7 @@ describe('actions/IOU', () => { transaction: transactionToCategorize, currentUserAccountID: RORY_ACCOUNT_ID, currentUserEmail: RORY_EMAIL, + currentUserLocalCurrency: '', }); await waitForBatchedUpdates(); @@ -534,6 +535,7 @@ describe('actions/IOU', () => { transaction: originalTransaction, currentUserAccountID: RORY_ACCOUNT_ID, currentUserEmail: RORY_EMAIL, + currentUserLocalCurrency: '', }); await waitForBatchedUpdates(); @@ -573,6 +575,7 @@ describe('actions/IOU', () => { transaction: undefined, currentUserAccountID: RORY_ACCOUNT_ID, currentUserEmail: RORY_EMAIL, + currentUserLocalCurrency: '', }); await waitForBatchedUpdates(); @@ -607,6 +610,7 @@ describe('actions/IOU', () => { amountOwed: 0, currentUserAccountID: RORY_ACCOUNT_ID, currentUserEmail: RORY_EMAIL, + currentUserLocalCurrency: '', }); await waitForBatchedUpdates(); diff --git a/tests/actions/IOUTest/TrackExpenseTest.ts b/tests/actions/IOUTest/TrackExpenseTest.ts index 11abfa12bcbb..8acd079bb8dc 100644 --- a/tests/actions/IOUTest/TrackExpenseTest.ts +++ b/tests/actions/IOUTest/TrackExpenseTest.ts @@ -271,6 +271,7 @@ describe('actions/IOU/TrackExpense', () => { transaction, currentUserAccountID: RORY_ACCOUNT_ID, currentUserEmail: RORY_EMAIL, + currentUserLocalCurrency: '', }); await waitForBatchedUpdates(); @@ -1114,6 +1115,7 @@ describe('actions/IOU/TrackExpense', () => { transaction: createdTransaction, currentUserAccountID: RORY_ACCOUNT_ID, currentUserEmail: RORY_EMAIL, + currentUserLocalCurrency: '', }); await waitForBatchedUpdates(); diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index b8b1158bac89..c3aab3550528 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -2452,16 +2452,16 @@ describe('actions/Policy', () => { await waitForBatchedUpdates(); const policyID = Policy.generatePolicyID(); - const params = Policy.createDraftWorkspace( - {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, - WORKSPACE_NAME, - ESH_ACCOUNT_ID, - ESH_EMAIL, - ESH_EMAIL, - true, + const params = Policy.createDraftWorkspace({ + introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, + workspaceName: WORKSPACE_NAME, + currentUserAccountID: ESH_ACCOUNT_ID, + currentUserEmail: ESH_EMAIL, + policyOwnerEmail: ESH_EMAIL, + makeMeAdmin: true, policyID, - CONST.CURRENCY.USD, - ); + currency: CONST.CURRENCY.USD, + }); await waitForBatchedUpdates(); const draft = await getOnyxValue(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`); @@ -2489,7 +2489,15 @@ describe('actions/Policy', () => { await waitForBatchedUpdates(); const policyID = Policy.generatePolicyID(); - Policy.createDraftWorkspace({choice: CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE}, WORKSPACE_NAME, ESH_ACCOUNT_ID, ESH_EMAIL, ESH_EMAIL, false, policyID, CONST.CURRENCY.EUR); + Policy.createDraftWorkspace({ + introSelected: {choice: CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE}, + workspaceName: WORKSPACE_NAME, + currentUserAccountID: ESH_ACCOUNT_ID, + currentUserEmail: ESH_EMAIL, + policyOwnerEmail: ESH_EMAIL, + policyID, + currency: CONST.CURRENCY.EUR, + }); await waitForBatchedUpdates(); const draft = await getOnyxValue(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`); @@ -2510,7 +2518,14 @@ describe('actions/Policy', () => { const customEmail = 'custom@example.com'; const policyID = Policy.generatePolicyID(); - Policy.createDraftWorkspace({choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, WORKSPACE_NAME, customAccountID, customEmail, customEmail, false, policyID); + Policy.createDraftWorkspace({ + introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, + workspaceName: WORKSPACE_NAME, + currentUserAccountID: customAccountID, + currentUserEmail: customEmail, + policyOwnerEmail: customEmail, + policyID, + }); await waitForBatchedUpdates(); const draft = await getOnyxValue(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`); @@ -2531,6 +2546,33 @@ describe('actions/Policy', () => { expect(draft?.ownerAccountID).not.toBe(ESH_ACCOUNT_ID); expect(draft?.employeeList?.[ESH_EMAIL]).toBeUndefined(); }); + + it('should use the explicit currency argument and not fall back to the deprecated session user localCurrencyCode', async () => { + // Set Onyx session + personal details so localCurrencyCode would be EUR if the fallback were used + await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID}); + await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [ESH_ACCOUNT_ID]: {accountID: ESH_ACCOUNT_ID, login: ESH_EMAIL, localCurrencyCode: CONST.CURRENCY.EUR}, + }); + await waitForBatchedUpdates(); + + const policyID = Policy.generatePolicyID(); + // Pass explicit GBP currency — this should win over the session user's EUR localCurrencyCode + Policy.createDraftWorkspace({ + introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, + workspaceName: WORKSPACE_NAME, + currentUserAccountID: ESH_ACCOUNT_ID, + currentUserEmail: ESH_EMAIL, + policyOwnerEmail: ESH_EMAIL, + policyID, + currency: 'GBP', + }); + await waitForBatchedUpdates(); + + const draft = await getOnyxValue(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`); + + expect(draft?.outputCurrency).toBe('GBP'); + expect(draft?.outputCurrency).not.toBe(CONST.CURRENCY.EUR); + }); }); describe('upgradeToCorporate', () => { diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 3c5f78a8dc33..543f7fbe5586 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -15018,6 +15018,7 @@ describe('ReportUtils', () => { transaction: undefined, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); expect(Navigation.navigate).not.toHaveBeenCalled(); @@ -15058,6 +15059,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should navigate to the restricted action page @@ -15094,6 +15096,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should navigate to the restricted action page @@ -15134,6 +15137,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should navigate to the category step @@ -15176,6 +15180,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should automatically pick the available policy and navigate to the category step @@ -15205,6 +15210,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should navigate to the upgrade page because no policies were found to categorize with @@ -15255,6 +15261,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should navigate to the upgrade page because it's ambiguous which policy to use @@ -15301,6 +15308,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should log a warning and not navigate @@ -15347,6 +15355,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should NOT navigate to restricted action page, but to category step @@ -15385,6 +15394,7 @@ describe('ReportUtils', () => { transaction, currentUserAccountID, currentUserEmail, + currentUserLocalCurrency: '', }); // Then it should navigate to restricted action page