diff --git a/src/ROUTES.ts b/src/ROUTES.ts index f057aad291208..641e74f94a1d8 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -187,6 +187,10 @@ const ROUTES = { return `bank-account/enter-signer-info?policyID=${policyID}&bankAccountID=${bankAccountID}&isCompleted=${isCompleted}` as const; }, }, + BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT: { + route: 'bank-account/connect-existing-business-bank-account', + getRoute: (policyID: string) => `bank-account/connect-existing-business-bank-account?policyID=${policyID}` as const, + }, PUBLIC_CONSOLE_DEBUG: { route: 'troubleshoot/console', @@ -1730,10 +1734,6 @@ const ROUTES = { return `workspaces/${policyID}/workflows` as const; }, }, - WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: { - route: 'workspaces/:policyID/workflows/connect-account', - getRoute: (policyID: string) => `workspaces/${policyID}/workflows/connect-account` as const, - }, WORKSPACE_WORKFLOWS_APPROVALS_NEW: { route: 'workspaces/:policyID/workflows/approvals/new', getRoute: (policyID: string) => `workspaces/${policyID}/workflows/approvals/new` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 4643456897cac..860419234f2dc 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -673,7 +673,6 @@ const SCREENS = { WORKFLOWS_APPROVALS_OVER_LIMIT_APPROVER: 'Workspace_Workflows_Approvals_Over_Limit_Approver', WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency', WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset', - WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: 'Workspace_Workflows_Connect_Existing_Bank_Account', DESCRIPTION: 'Workspace_Overview_Description', SHARE: 'Workspace_Overview_Share', NAME: 'Workspace_Overview_Name', @@ -799,6 +798,7 @@ const SCREENS = { ADD_PERSONAL_BANK_ACCOUNT_ROOT: 'AddPersonalBankAccount_Root', REIMBURSEMENT_ACCOUNT_ROOT: 'Reimbursement_Account_Root', REIMBURSEMENT_ACCOUNT_VERIFY_ACCOUNT: 'Reimbursement_Account_Verify_Account', + CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT: 'Connect_Existing_Business_Bank_Account_Root', WALLET_STATEMENT_ROOT: 'WalletStatement_Root', SIGN_IN_ROOT: 'SignIn_Root', DETAILS_ROOT: 'Details_Root', diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx index ac4814c1a8f5e..4514daa7ae236 100644 --- a/src/components/KYCWall/BaseKYCWall.tsx +++ b/src/components/KYCWall/BaseKYCWall.tsx @@ -10,13 +10,16 @@ import useOnyx from '@hooks/useOnyx'; import useParentReportAction from '@hooks/useParentReportAction'; import {openPersonalBankAccountSetupView} from '@libs/actions/BankAccounts'; import {completePaymentOnboarding, savePreferredPaymentMethod} from '@libs/actions/IOU'; +import {navigateToBankAccountRoute} from '@libs/actions/ReimbursementAccount'; import {moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report'; +import {isBankAccountPartiallySetup} from '@libs/BankAccountUtils'; import getClickedTargetLocation from '@libs/getClickedTargetLocation'; import Log from '@libs/Log'; import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNavigationActionToMicrotaskQueue'; import Navigation from '@libs/Navigation/Navigation'; import {hasExpensifyPaymentMethod} from '@libs/PaymentUtils'; import {getBankAccountRoute, isExpenseReport as isExpenseReportReportUtils, isIOUReport} from '@libs/ReportUtils'; +import {getEligibleExistingBusinessBankAccounts, getOpenConnectedToPolicyBusinessBankAccounts} from '@libs/WorkflowUtils'; import {createWorkspaceFromIOUPayment} from '@userActions/Policy/Policy'; import {setKYCWallSource} from '@userActions/Wallet'; import CONST from '@src/CONST'; @@ -51,15 +54,17 @@ function KYCWall({ source, shouldShowPersonalBankAccountOption = false, ref, + currency, }: KYCWallProps) { const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true}); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS, {canBeMissing: true}); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true}); const [bankAccountList = getEmptyObject()] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true}); - const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true}); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, {canBeMissing: true}); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); + const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true}); + const {formatPhoneNumber} = useLocalize(); const currentUserDetails = useCurrentUserPersonalDetails(); const currentUserEmail = currentUserDetails.email ?? ''; @@ -115,6 +120,8 @@ function KYCWall({ setPositionAddPaymentMenu(position); }, [getAnchorPosition]); + const canLinkExistingBusinessBankAccount = getEligibleExistingBusinessBankAccounts(bankAccountList, currency, true).length > 0; + const selectPaymentMethod = useCallback( (paymentMethod?: PaymentMethod, policy?: Policy) => { if (paymentMethod) { @@ -169,6 +176,22 @@ function KYCWall({ Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID)); return; } + + // If user has a setup in progress for we redirect to the flow where setup can be finished + // Setup is in progress in 2 cases: + // - account already present on policy is partially setup + // - account is being connected 'on the spot' while trying to pay for an expense (it won't be linked to policy yet but will appear as reimbursementAccount) + if (policy !== undefined && (isBankAccountPartiallySetup(policy?.achAccount?.state) || isBankAccountPartiallySetup(reimbursementAccount?.achData?.state))) { + navigateToBankAccountRoute(policy.id); + return; + } + + // If user has existing bank accounts that he can connect we show the list of these accounts + if (policy !== undefined && canLinkExistingBusinessBankAccount) { + Navigation.navigate(ROUTES.BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT.getRoute(policy?.id)); + return; + } + const bankAccountRoute = addBankAccountRoute ?? getBankAccountRoute(chatReport); Navigation.navigate(bankAccountRoute); } @@ -177,6 +200,8 @@ function KYCWall({ onSelectPaymentMethod, iouReport, addDebitCardRoute, + reimbursementAccount?.achData?.state, + canLinkExistingBusinessBankAccount, addBankAccountRoute, chatReport, policies, @@ -218,11 +243,14 @@ function KYCWall({ const isExpenseReport = isExpenseReportReportUtils(iouReport); const paymentCardList = fundList ?? {}; + const hasOpenConnectedBusinessBankAccount = getOpenConnectedToPolicyBusinessBankAccounts(bankAccountList, policy).length > 0; const hasValidPaymentMethod = hasExpensifyPaymentMethod(paymentCardList, bankAccountList, shouldIncludeDebitCard); const isFromWalletPage = source === CONST.KYC_WALL_SOURCE.ENABLE_WALLET || source === CONST.KYC_WALL_SOURCE.TRANSFER_BALANCE; - // Check to see if user has a valid payment method on file and display the add payment popover if they don't - if ((isExpenseReport && reimbursementAccount?.achData?.state !== CONST.BANK_ACCOUNT.STATE.OPEN) || (!isExpenseReport && bankAccountList !== null && !hasValidPaymentMethod)) { + // Check if the user needs to add or select a payment method before continuing. + // - For expense reports: Proceeds if no accounts that are connected are valid and usable (`OPEN`) + // - For other expenses: Proceeds if the user lacks a valid personal bank account or debit card + if ((isExpenseReport && !hasOpenConnectedBusinessBankAccount) || (!isExpenseReport && bankAccountList !== null && !hasValidPaymentMethod)) { Log.info('[KYC Wallet] User does not have valid payment method'); if (!shouldIncludeDebitCard || (isFromWalletPage && !hasValidPaymentMethod)) { @@ -286,7 +314,6 @@ function KYCWall({ getAnchorPosition, iouReport, onSuccessfulKYC, - reimbursementAccount?.achData?.state, selectPaymentMethod, shouldIncludeDebitCard, shouldShowAddPaymentMenu, diff --git a/src/components/KYCWall/types.ts b/src/components/KYCWall/types.ts index 59f3485442d66..80f6be6afbc95 100644 --- a/src/components/KYCWall/types.ts +++ b/src/components/KYCWall/types.ts @@ -77,6 +77,9 @@ type KYCWallProps = { /** Reference to the KYCWall component */ ref: ForwardedRef; + + /** Currency associated with the payment */ + currency?: string; }; type KYCWallRef = { diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index c02c8db81fb60..4e7371421edb5 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -371,14 +371,14 @@ function Search({ }, [isSmallScreenWidth]); useEffect(() => { - openSearch(); + openSearch({includePartiallySetupBankAccounts: true}); }, []); useEffect(() => { if (!prevIsOffline || isOffline) { return; } - openSearch(); + openSearch({includePartiallySetupBankAccounts: true}); }, [isOffline, prevIsOffline]); const {newSearchResultKeys, handleSelectionListScroll, newTransactions} = useSearchHighlightAndScroll({ diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 3a3980662dad7..362189c173291 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -145,7 +145,7 @@ function SettlementButton({ const hasSinglePolicy = !isExpenseReport && activeAdminPolicies.length === 1; const hasMultiplePolicies = !isExpenseReport && activeAdminPolicies.length > 1; const formattedPaymentMethods = formatPaymentMethods(bankAccountList ?? {}, fundList ?? {}, styles, translate); - const hasIntentToPay = ((formattedPaymentMethods.length === 1 && isIOUReport(iouReport)) || !!policy?.achAccount) && !lastPaymentMethod; + const hasIntentToPay = ((formattedPaymentMethods.length === 1 && isIOUReport(iouReport)) || policy?.achAccount?.state === CONST.BANK_ACCOUNT.STATE.OPEN) && !lastPaymentMethod; const {isBetaEnabled} = usePermissions(); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); @@ -165,7 +165,9 @@ function SettlementButton({ if (!policy?.achAccount?.bankAccountID) { return; } - const policyBankAccounts = formattedPaymentMethods.filter((method) => method.methodID === policy?.achAccount?.bankAccountID); + const policyBankAccounts = formattedPaymentMethods.filter( + (method) => method.methodID === policy?.achAccount?.bankAccountID && (method.accountData as AccountData)?.state === CONST.BANK_ACCOUNT.STATE.OPEN, + ); return policyBankAccounts.map((formattedPaymentMethod) => { const {icon, iconStyles, iconSize, title, description, methodID} = formattedPaymentMethod ?? {}; @@ -513,7 +515,9 @@ function SettlementButton({ return lastPaymentPolicy.name; } - const bankAccountToDisplay = hasIntentToPay ? (formattedPaymentMethods.at(0) as BankAccount) : bankAccount; + const bankAccountToDisplay = hasIntentToPay + ? ((formattedPaymentMethods.find((method) => method.methodID === policy?.achAccount?.bankAccountID) ?? formattedPaymentMethods.at(0)) as BankAccount) + : bankAccount; if (lastPaymentMethod === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || (hasIntentToPay && (isExpenseReport || isInvoiceReport))) { if (isInvoiceReport) { @@ -539,7 +543,7 @@ function SettlementButton({ return translate('paymentMethodList.bankAccountLastFour', bankAccountToDisplay?.accountData?.accountNumber?.slice(-4)); } - if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && isExpenseReportUtil(iouReport)) { + if (bankAccount?.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && bankAccount?.methodID === policy?.achAccount?.bankAccountID && isExpenseReportUtil(iouReport)) { return translate('paymentMethodList.bankAccountLastFour', bankAccount?.accountData?.accountNumber?.slice(-4) ?? ''); } @@ -551,9 +555,13 @@ function SettlementButton({ return; } - const {paymentType, selectedPolicy, shouldSelectPaymentMethod} = getActivePaymentType(selectedOption, activeAdminPolicies, latestBankItem); + const {paymentType, selectedPolicy, shouldSelectPaymentMethod} = getActivePaymentType(selectedOption, activeAdminPolicies, latestBankItem, policyIDKey); - if (!!selectedPolicy || shouldSelectPaymentMethod) { + // Payment type for 'Pay via workspace' option is "Elsewhere" but selected option points to one of workspaces where user is admin + const isPayingViaWorkspace = paymentType === CONST.IOU.PAYMENT_TYPE.ELSEWHERE && activeAdminPolicies.find((activeAdminPolicy) => activeAdminPolicy.id === selectedOption); + const isPayingWithMethod = paymentType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE; + + if ((!!selectedPolicy || shouldSelectPaymentMethod) && (isPayingWithMethod || isPayingViaWorkspace)) { selectPaymentMethod(event, paymentType, triggerKYCFlow, selectedOption as PaymentMethod, selectedPolicy); return; } @@ -597,6 +605,7 @@ function SettlementButton({ policy={lastPaymentPolicy} anchorAlignment={kycWallAnchorAlignment} shouldShowPersonalBankAccountOption={shouldShowPersonalBankAccountOption} + currency={currency} > {(triggerKYCFlow, buttonRef) => ( diff --git a/src/libs/API/parameters/OpenSearchPageParams.ts b/src/libs/API/parameters/OpenSearchPageParams.ts new file mode 100644 index 0000000000000..8341b290180c8 --- /dev/null +++ b/src/libs/API/parameters/OpenSearchPageParams.ts @@ -0,0 +1,5 @@ +type OpenSearchPageParams = { + includePartiallySetupBankAccounts: boolean; +}; + +export default OpenSearchPageParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index bece054a02378..b63e384818b20 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -451,6 +451,7 @@ export type {default as SetSamlIdentityParams} from './SetSamlIdentityParams'; export type {default as UpdateSamlEnabledParams} from './UpdateSamlEnabledParams'; export type {default as AddAdminToDomainParams} from './AddAdminToDomainParams'; export type {default as UpdateSamlRequiredParams} from './UpdateSamlRequiredParams'; +export type {default as OpenSearchPageParams} from './OpenSearchPageParams'; export type {default as SetPolicyRequireCompanyCardsEnabledParams} from './SetPolicyRequireCompanyCardsEnabled'; export type {default as SetTechnicalContactEmailParams} from './SetTechnicalContactEmailParams'; export type {default as ToggleConsolidatedDomainBillingParams} from './ToggleConsolidatedDomainBillingParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 0e2fca6d06cd1..a0a92b0bfe589 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1203,7 +1203,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_ONFIDO_FLOW]: null; [READ_COMMANDS.OPEN_INITIAL_SETTINGS_PAGE]: null; [READ_COMMANDS.OPEN_ENABLE_PAYMENTS_PAGE]: null; - [READ_COMMANDS.OPEN_SEARCH_PAGE]: null; + [READ_COMMANDS.OPEN_SEARCH_PAGE]: Parameters.OpenSearchPageParams; [READ_COMMANDS.SEARCH]: Parameters.SearchParams; [READ_COMMANDS.BEGIN_SIGNIN]: Parameters.BeginSignInParams; [READ_COMMANDS.SIGN_IN_WITH_SHORT_LIVED_AUTH_TOKEN]: Parameters.SignInWithShortLivedAuthTokenParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 59e3251ff9059..c4cf60aedee88 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -545,8 +545,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopCustomersDisplayedAsPage').default, [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_ITEMS]: () => require('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage').default, - [SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: () => - require('../../../../pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage').default, + [SCREENS.CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT]: () => require('@pages/workspace/ConnectExistingBusinessBankAccountPage').default, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../../pages/ReimbursementAccount/ReimbursementAccountPage').default, [SCREENS.REIMBURSEMENT_ACCOUNT_VERIFY_ACCOUNT]: () => require('../../../../pages/ReimbursementAccount/ReimbursementAccountVerifyAccountPage').default, [SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO]: () => require('../../../../pages/ReimbursementAccount/EnterSignerInfo').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index 82742ec57e4f7..3ec54d858f82a 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -36,7 +36,6 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE]: { path: ROUTES.WORKSPACE_EDIT_REPORT_FIELDS_INITIAL_VALUE.route, }, - [SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: { - path: ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.route, + [SCREENS.CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT]: { + path: ROUTES.BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT.route, exact: true, }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b956be04f75e5..f24b231bb4901 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -2031,6 +2031,12 @@ type ReimbursementAccountEnterSignerInfoNavigatorParamList = { }; }; +type ConnectExistingBankAccountNavigatorParamList = { + [SCREENS.CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT]: { + policyID: string; + }; +}; + type WalletStatementNavigatorParamList = { [SCREENS.WALLET_STATEMENT_ROOT]: { /** The statement year and month as one string, i.e. 202110 */ @@ -2409,9 +2415,6 @@ type WorkspaceSplitNavigatorParamList = { [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: { policyID: string; }; - [SCREENS.WORKSPACE.WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT]: { - policyID: string; - }; [SCREENS.WORKSPACE.INVOICES]: { policyID: string; }; @@ -2941,6 +2944,7 @@ export type { ReportVerifyAccountNavigatorParamList, ReimbursementAccountNavigatorParamList, ReimbursementAccountEnterSignerInfoNavigatorParamList, + ConnectExistingBankAccountNavigatorParamList, NewReportWorkspaceSelectionNavigatorParamList, ReportDescriptionNavigatorParamList, ReportDetailsNavigatorParamList, diff --git a/src/libs/PaymentUtils.ts b/src/libs/PaymentUtils.ts index eeda990d6ffdf..3c522efa96863 100644 --- a/src/libs/PaymentUtils.ts +++ b/src/libs/PaymentUtils.ts @@ -221,10 +221,11 @@ const isSecondaryActionAPaymentOption = (item: PopoverMenuItem): item is Payment * Get the appropriate payment type, selected policy, and whether a payment method should be selected * based on the provided payment method, active admin policies, and latest bank items. */ -function getActivePaymentType(paymentMethod: string | undefined, activeAdminPolicies: Policy[], latestBankItems: BankAccountMenuItem[] | undefined) { +function getActivePaymentType(paymentMethod: string | undefined, activeAdminPolicies: Policy[], latestBankItems: BankAccountMenuItem[] | undefined, policyID?: string | undefined) { const isPaymentMethod = Object.values(CONST.PAYMENT_METHODS).includes(paymentMethod as ValueOf); const shouldSelectPaymentMethod = isPaymentMethod || !isEmpty(latestBankItems); - const selectedPolicy = activeAdminPolicies.find((activePolicy) => activePolicy.id === paymentMethod); + // payment method is equal to policyID when user selects "Pay via workspace" option + const selectedPolicy = activeAdminPolicies.find((activePolicy) => activePolicy.id === policyID || activePolicy.id === paymentMethod); let paymentType; switch (paymentMethod) { diff --git a/src/libs/WorkflowUtils.ts b/src/libs/WorkflowUtils.ts index a433ccb7e8466..4f526446a0351 100644 --- a/src/libs/WorkflowUtils.ts +++ b/src/libs/WorkflowUtils.ts @@ -521,12 +521,38 @@ function getApprovalLimitDescription({approver, currency, translate, personalDet }); } +/** + * Returns business bank accounts that are: + * - It has the same currency as the policy (`bankCurrency === policy.outputCurrency`), + * - Its state is `OPEN` + * - Its type is `BUSINESS`, + * - It's linked to the policy's ACH account. + * + * @param bankAccountList - list of bank accounts + * @param policy - given policy + */ +function getOpenConnectedToPolicyBusinessBankAccounts(bankAccountList: BankAccountList | undefined, policy: OnyxEntry | undefined) { + if (!bankAccountList || policy === undefined) { + return []; + } + + return Object.values(bankAccountList).filter((account) => { + return ( + account.bankCurrency === policy?.outputCurrency && + account.accountData?.state === CONST.BANK_ACCOUNT.STATE.OPEN && + account.accountData?.type === CONST.BANK_ACCOUNT.TYPE.BUSINESS && + account?.accountData?.bankAccountID === policy?.achAccount?.bankAccountID + ); + }); +} + export { calculateApprovers, convertPolicyEmployeesToApprovalWorkflows, convertApprovalWorkflowToPolicyEmployees, getApprovalLimitDescription, getEligibleExistingBusinessBankAccounts, + getOpenConnectedToPolicyBusinessBankAccounts, INITIAL_APPROVAL_WORKFLOW, updateWorkflowDataOnApproverRemoval, }; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index c64a6b8d69044..4d033fae568d7 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -25,7 +25,6 @@ import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {MemberForList} from '@libs/OptionsListUtils'; -import {getPersonalPolicy} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -33,7 +32,7 @@ import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {InternationalBankAccountForm, PersonalBankAccountForm} from '@src/types/form'; import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, ReimbursementAccountForm, RequestorStepProps} from '@src/types/form/ReimbursementAccountForm'; -import type {BankAccountList, LastPaymentMethod, LastPaymentMethodType, PersonalBankAccount} from '@src/types/onyx'; +import type {BankAccountList, LastPaymentMethod, LastPaymentMethodType, PersonalBankAccount, Policy} from '@src/types/onyx'; import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount'; import type {BankAccountStep, ReimbursementAccountStep, ReimbursementAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; import type {OnyxData} from '@src/types/onyx/Request'; @@ -216,7 +215,7 @@ function addBusinessWebsiteForDraft(websiteUrl: string) { /** * Get the Onyx data required to set the last used payment method to VBBA for a given policyID */ -function getOnyxDataForConnectingBankAccount(policyID: string, lastPaymentMethod?: LastPaymentMethodType | string): OnyxData { +function getOnyxDataForConnectingVBBAAndLastPaymentMethod(policyID: string, lastPaymentMethod?: LastPaymentMethodType | string): OnyxData { const onyxData = getVBBADataForOnyx(); const lastUsedPaymentMethod = typeof lastPaymentMethod === 'string' ? lastPaymentMethod : lastPaymentMethod?.expense?.name; @@ -243,7 +242,7 @@ function getOnyxDataForConnectingBankAccount(policyID: string, lastPaymentMethod /** * Submit Bank Account step with Plaid data so php can perform some checks. */ -function connectBankAccountWithPlaid(bankAccountID: number, selectedPlaidBankAccount: PlaidBankAccount, policyID: string, lastPaymentMethod?: LastPaymentMethodType | string) { +function connectBankAccountWithPlaid(bankAccountID: number, selectedPlaidBankAccount: PlaidBankAccount, policyID: string) { const parameters: ConnectBankAccountParams = { bankAccountID, routingNumber: selectedPlaidBankAccount.routingNumber, @@ -256,9 +255,7 @@ function connectBankAccountWithPlaid(bankAccountID: number, selectedPlaidBankAcc policyID, }; - const onyxData = getOnyxDataForConnectingBankAccount(policyID, lastPaymentMethod); - - API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID, parameters, onyxData); + API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_WITH_PLAID, parameters); } /** @@ -266,7 +263,7 @@ function connectBankAccountWithPlaid(bankAccountID: number, selectedPlaidBankAcc * * TODO: offline pattern for this command will have to be added later once the pattern B design doc is complete */ -function addPersonalBankAccount(account: PlaidBankAccount, policyID?: string, source?: string, lastPaymentMethod?: LastPaymentMethodType | string | undefined) { +function addPersonalBankAccount(account: PlaidBankAccount, policyID?: string, source?: string, lastPaymentMethod?: LastPaymentMethodType | string | undefined, personalPolicy?: Policy) { const parameters: AddPersonalBankAccountParams = { addressName: account.addressName ?? '', routingNumber: account.routingNumber, @@ -284,8 +281,6 @@ function addPersonalBankAccount(account: PlaidBankAccount, policyID?: string, so parameters.source = source; } - const personalPolicy = getPersonalPolicy(); - const onyxData: OnyxData = { optimisticData: [ { @@ -369,7 +364,7 @@ function addPersonalBankAccount(account: PlaidBankAccount, policyID?: string, so API.write(WRITE_COMMANDS.ADD_PERSONAL_BANK_ACCOUNT, parameters, onyxData); } -function deletePaymentBankAccount(bankAccountID: number, lastUsedPaymentMethods?: LastPaymentMethod, bankAccount?: OnyxEntry) { +function deletePaymentBankAccount(bankAccountID: number, lastUsedPaymentMethods?: LastPaymentMethod, bankAccount?: OnyxEntry, personalPolicy?: Policy) { const parameters: DeletePaymentBankAccountParams = {bankAccountID}; const bankAccountFailureData = { @@ -378,8 +373,6 @@ function deletePaymentBankAccount(bankAccountID: number, lastUsedPaymentMethods? pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }; - const personalPolicy = getPersonalPolicy(); - const onyxData: OnyxData = { optimisticData: [ { @@ -1076,8 +1069,13 @@ function updateBeneficialOwnersForBankAccount(bankAccountID: number, params: Par /** * Accept the ACH terms and conditions and verify the accuracy of the information provided * @param params - Verification step form params + * @param bankAccountID - ID for bank account + * @param policyID - ID of the policy we're setting the bank account on + * @param lastPaymentMethod - last payment method used in the app */ -function acceptACHContractForBankAccount(bankAccountID: number, params: ACHContractStepProps, policyID: string | undefined) { +function acceptACHContractForBankAccount(bankAccountID: number, params: ACHContractStepProps, policyID: string, lastPaymentMethod?: LastPaymentMethodType | string) { + const onyxData = getOnyxDataForConnectingVBBAAndLastPaymentMethod(policyID, lastPaymentMethod); + API.write( WRITE_COMMANDS.ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT, { @@ -1085,14 +1083,14 @@ function acceptACHContractForBankAccount(bankAccountID: number, params: ACHContr bankAccountID, policyID, }, - getVBBADataForOnyx(), + onyxData, ); } /** * Create the bank account with manually entered data. */ -function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBankAccount, policyID: string, lastPaymentMethod?: LastPaymentMethodType | string) { +function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBankAccount, policyID: string) { const parameters: ConnectBankAccountParams = { bankAccountID, routingNumber: bankAccount.routingNumber, @@ -1105,9 +1103,7 @@ function connectBankAccountManually(bankAccountID: number, bankAccount: PlaidBan policyID, }; - const onyxData = getOnyxDataForConnectingBankAccount(policyID, lastPaymentMethod); - - API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_MANUALLY, parameters, onyxData); + API.write(WRITE_COMMANDS.CONNECT_BANK_ACCOUNT_MANUALLY, parameters); } /** diff --git a/src/libs/actions/ReimbursementAccount/resetNonUSDBankAccount.ts b/src/libs/actions/ReimbursementAccount/resetNonUSDBankAccount.ts index 89a090469ec20..2e157acd4cde6 100644 --- a/src/libs/actions/ReimbursementAccount/resetNonUSDBankAccount.ts +++ b/src/libs/actions/ReimbursementAccount/resetNonUSDBankAccount.ts @@ -4,9 +4,11 @@ import * as API from '@libs/API'; import {WRITE_COMMANDS} from '@libs/API/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; import type {ACHAccount} from '@src/types/onyx/Policy'; +import type {OnyxData} from '@src/types/onyx/Request'; -function resetNonUSDBankAccount(policyID: string | undefined, achAccount: OnyxEntry, bankAccountID?: number) { +function resetNonUSDBankAccount(policyID: string | undefined, achAccount: OnyxEntry, bankAccountID?: number, lastUsedPaymentMethod?: OnyxTypes.LastPaymentMethodType) { if (!policyID) { throw new Error('Missing policy when attempting to reset'); } @@ -36,57 +38,75 @@ function resetNonUSDBankAccount(policyID: string | undefined, achAccount: OnyxEn return; } - API.write( - WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP, - {policyID, bankAccountID}, - { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - shouldShowResetModal: false, - isLoading: true, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - achData: null, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - achAccount: null, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, - value: null, + const isLastUsedPaymentMethodVBBA = lastUsedPaymentMethod?.expense?.name === CONST.IOU.PAYMENT_TYPE.VBBA; + const isPreviousLastUsedPaymentMethodVBBA = lastUsedPaymentMethod?.lastUsed?.name === CONST.IOU.PAYMENT_TYPE.VBBA; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + shouldShowResetModal: false, + isLoading: true, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + achData: null, }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + achAccount: null, }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: {isLoading: false, pendingAction: null}, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, + value: null, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: {isLoading: false, pendingAction: null}, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + achAccount, }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - achAccount, + }, + ], + }; + + if (isLastUsedPaymentMethodVBBA) { + onyxData.successData?.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD, + value: { + [policyID]: { + expense: { + name: isPreviousLastUsedPaymentMethodVBBA ? '' : lastUsedPaymentMethod?.lastUsed.name, + }, + lastUsed: { + name: isPreviousLastUsedPaymentMethodVBBA ? '' : lastUsedPaymentMethod?.lastUsed.name, }, }, - ], - }, - ); + }, + }); + } + + API.write(WRITE_COMMANDS.RESTART_BANK_ACCOUNT_SETUP, {policyID, bankAccountID}, onyxData); } export default resetNonUSDBankAccount; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9179e7583e9b1..dda87e095c339 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1046,7 +1046,7 @@ function openReport( accountIDList: participantAccountIDList ? participantAccountIDList.join(',') : '', parentReportActionID, transactionID: transaction?.transactionID, - includePartiallySetupBankAccounts: isConciergeChatReport(allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]), + includePartiallySetupBankAccounts: true, }; if (optimisticSelfDMReport) { diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index b035ddb48284c..da96fc357afb6 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -10,7 +10,7 @@ import type {BankAccountMenuItem, PaymentData, SearchQueryJSON, SelectedReports, import type {TransactionListItemType, TransactionReportGroupListItemType} from '@components/SelectionListWithSections/types'; import * as API from '@libs/API'; import {waitForWrites} from '@libs/API'; -import type {ExportSearchItemsToCSVParams, ExportSearchWithTemplateParams, ReportExportParams, SubmitReportParams} from '@libs/API/parameters'; +import type {ExportSearchItemsToCSVParams, ExportSearchWithTemplateParams, OpenSearchPageParams, ReportExportParams, SubmitReportParams} from '@libs/API/parameters'; import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import {getCommandURL} from '@libs/ApiUtils'; import {convertToDisplayString} from '@libs/CurrencyUtils'; @@ -376,8 +376,8 @@ function deleteSavedSearch(hash: number) { API.write(WRITE_COMMANDS.DELETE_SAVED_SEARCH, {hash}, {optimisticData, failureData, successData}); } -function openSearchPage() { - API.read(READ_COMMANDS.OPEN_SEARCH_PAGE, null); +function openSearchPage({includePartiallySetupBankAccounts}: OpenSearchPageParams) { + API.read(READ_COMMANDS.OPEN_SEARCH_PAGE, {includePartiallySetupBankAccounts}); } function search({ diff --git a/src/pages/AddPersonalBankAccountPage.tsx b/src/pages/AddPersonalBankAccountPage.tsx index 0fb31204f73a4..3649a4a0fd95f 100644 --- a/src/pages/AddPersonalBankAccountPage.tsx +++ b/src/pages/AddPersonalBankAccountPage.tsx @@ -10,6 +10,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import getPlaidOAuthReceivedRedirectURI from '@libs/getPlaidOAuthReceivedRedirectURI'; import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName'; @@ -27,6 +28,8 @@ function AddPersonalBankAccountPage() { const [selectedPlaidAccountId, setSelectedPlaidAccountId] = useState(''); const [personalBankAccount] = useOnyx(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {canBeMissing: true}); const [plaidData] = useOnyx(ONYXKEYS.PLAID_DATA, {canBeMissing: true}); + const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); + const personalPolicy = usePolicy(personalPolicyID); const shouldShowSuccess = personalBankAccount?.shouldShowSuccess ?? false; const topmostFullScreenRoute = navigationRef.current?.getRootState()?.routes.findLast((route) => isFullScreenName(route.name)); const kycWallRef = useContext(KYCWallContext); @@ -59,9 +62,9 @@ function AddPersonalBankAccountPage() { ...selectedPlaidBankAccount, plaidAccessToken: plaidData?.plaidAccessToken ?? '', }; - addPersonalBankAccount(bankAccountWithToken, policyID, source); + addPersonalBankAccount(bankAccountWithToken, policyID, source, undefined, personalPolicy); } - }, [plaidData?.bankAccounts, plaidData?.plaidAccessToken, selectedPlaidAccountId, personalBankAccount?.policyID, personalBankAccount?.source]); + }, [plaidData?.bankAccounts, plaidData?.plaidAccessToken, personalBankAccount?.policyID, personalBankAccount?.source, selectedPlaidAccountId, personalPolicy]); const exitFlow = useCallback( (shouldContinue = false) => { diff --git a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx index c01eab9c2b737..e6123e8dea67d 100644 --- a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx +++ b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx @@ -6,6 +6,7 @@ import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePolicy from '@hooks/usePolicy'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -25,6 +26,8 @@ function AddBankAccount() { const [plaidData] = useOnyx(ONYXKEYS.PLAID_DATA, {canBeMissing: true}); const [personalBankAccount] = useOnyx(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {canBeMissing: true}); const [personalBankAccountDraft] = useOnyx(ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT_FORM_DRAFT, {canBeMissing: true}); + const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); + const personalPolicy = usePolicy(personalPolicyID); const {translate} = useLocalize(); const styles = useThemeStyles(); const kycWallRef = useContext(KYCWallContext); @@ -40,9 +43,9 @@ function AddBankAccount() { ...selectedPlaidBankAccount, plaidAccessToken: plaidData?.plaidAccessToken ?? '', }; - addPersonalBankAccount(bankAccountWithToken); + addPersonalBankAccount(bankAccountWithToken, undefined, undefined, undefined, personalPolicy); } - }, [personalBankAccountDraft?.plaidAccountID, plaidData?.bankAccounts, plaidData?.plaidAccessToken]); + }, [personalBankAccountDraft?.plaidAccountID, personalPolicy, plaidData?.bankAccounts, plaidData?.plaidAccessToken]); const isSetupTypeChosen = personalBankAccountDraft?.setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx index 6d52954267870..7af50880d5a40 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx @@ -94,6 +94,7 @@ function ReimbursementAccountPage({route, policy, isLoadingPolicy, navigation}: certifyTrueInformation?: boolean; acceptTermsAndConditions?: boolean; }>({}); + const isLoadingWorkspaceReimbursement = policy?.isLoadingWorkspaceReimbursement; const isNonUSDWorkspace = policyCurrency !== CONST.CURRENCY.USD; const hasUnsupportedCurrency = isComingFromExpensifyCard && isBetaEnabled(CONST.BETAS.EXPENSIFY_CARD_EU_UK) && isNonUSDWorkspace @@ -451,7 +452,7 @@ function ReimbursementAccountPage({route, policy, isLoadingPolicy, navigation}: // or when data is being loaded. Don't show the loading indicator if we're offline and restarted the bank account setup process // On Android, when we open the app from the background, Onfido activity gets destroyed, so we need to reopen it. // eslint-disable-next-line react-compiler/react-compiler - if ((!hasACHDataBeenLoaded || isLoading) && shouldShowOfflineLoader && (shouldReopenOnfido || !requestorStepRef?.current)) { + if ((!hasACHDataBeenLoaded || isLoading || isLoadingWorkspaceReimbursement) && shouldShowOfflineLoader && (shouldReopenOnfido || !requestorStepRef?.current)) { return ; } diff --git a/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx b/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx index b59a9692e90c3..54273f008d806 100644 --- a/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx +++ b/src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx @@ -39,7 +39,6 @@ function BankInfo({onBackButtonPress, policyID, setUSDBankAccountStep}: BankInfo const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: false}); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, {canBeMissing: false}); const [plaidLinkToken] = useOnyx(ONYXKEYS.PLAID_LINK_TOKEN, {canBeMissing: true}); - const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const {translate} = useLocalize(); const [redirectedFromPlaidToManual, setRedirectedFromPlaidToManual] = React.useState(false); @@ -69,7 +68,6 @@ function BankInfo({onBackButtonPress, policyID, setUSDBankAccountStep}: BankInfo [BANK_INFO_STEP_KEYS.IS_SAVINGS]: data[BANK_INFO_STEP_KEYS.IS_SAVINGS] ?? false, }, policyID, - lastPaymentMethod?.[policyID], ); } else if (setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID) { connectBankAccountWithPlaid( @@ -84,11 +82,10 @@ function BankInfo({onBackButtonPress, policyID, setUSDBankAccountStep}: BankInfo [BANK_INFO_STEP_KEYS.IS_SAVINGS]: data[BANK_INFO_STEP_KEYS.IS_SAVINGS] ?? false, }, policyID, - lastPaymentMethod?.[policyID], ); } }, - [setupType, bankAccountID, lastPaymentMethod, values?.bankName, values?.plaidAccountID, values?.plaidAccessToken, values?.mask, policyID], + [setupType, bankAccountID, values?.bankName, values?.plaidAccountID, values?.plaidAccessToken, values?.mask, policyID], ); const bodyContent = setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID ? plaidSubSteps : manualSubSteps; diff --git a/src/pages/ReimbursementAccount/USD/CompleteVerification/CompleteVerification.tsx b/src/pages/ReimbursementAccount/USD/CompleteVerification/CompleteVerification.tsx index e6250f17ad5bc..559d38f745a13 100644 --- a/src/pages/ReimbursementAccount/USD/CompleteVerification/CompleteVerification.tsx +++ b/src/pages/ReimbursementAccount/USD/CompleteVerification/CompleteVerification.tsx @@ -25,11 +25,16 @@ function CompleteVerification({onBackButtonPress}: CompleteVerificationProps) { const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: false}); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, {canBeMissing: true}); + const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const values = useMemo(() => getSubStepValues(COMPLETE_VERIFICATION_KEYS, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); const policyID = reimbursementAccount?.achData?.policyID; const submit = useCallback(() => { + if (!policyID) { + return; + } + acceptACHContractForBankAccount( Number(reimbursementAccount?.achData?.bankAccountID), { @@ -38,8 +43,9 @@ function CompleteVerification({onBackButtonPress}: CompleteVerificationProps) { acceptTermsAndConditions: values.acceptTermsAndConditions, }, policyID, + lastPaymentMethod?.[policyID], ); - }, [reimbursementAccount?.achData?.bankAccountID, values.isAuthorizedToUseBankAccount, values.certifyTrueInformation, values.acceptTermsAndConditions, policyID]); + }, [reimbursementAccount?.achData?.bankAccountID, values.isAuthorizedToUseBankAccount, values.certifyTrueInformation, values.acceptTermsAndConditions, policyID, lastPaymentMethod]); const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo, goToTheLastStep} = useSubStep({bodyContent, startFrom: 0, onFinished: submit}); diff --git a/src/pages/settings/Wallet/WalletPage/index.tsx b/src/pages/settings/Wallet/WalletPage/index.tsx index a21cbf8cc8d96..0462b6a804e44 100644 --- a/src/pages/settings/Wallet/WalletPage/index.tsx +++ b/src/pages/settings/Wallet/WalletPage/index.tsx @@ -27,6 +27,7 @@ import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePaymentMethodState from '@hooks/usePaymentMethodState'; import type {FormattedSelectedPaymentMethod} from '@hooks/usePaymentMethodState/types'; +import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -66,6 +67,8 @@ function WalletPage() { const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: false}); const [userAccount] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); const [lastUsedPaymentMethods] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); + const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); + const personalPolicy = usePolicy(personalPolicyID); const isUserValidated = userAccount?.validated ?? false; const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); const {login: currentUserLogin} = useCurrentUserPersonalDetails(); @@ -206,7 +209,7 @@ function WalletPage() { const fundID = paymentMethod.selectedPaymentMethod.fundID; if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && bankAccountID) { const bankAccount = bankAccountList?.[paymentMethod.methodID] ?? {}; - deletePaymentBankAccount(bankAccountID, lastUsedPaymentMethods, bankAccount); + deletePaymentBankAccount(bankAccountID, lastUsedPaymentMethods, bankAccount, personalPolicy); } else if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.DEBIT_CARD && fundID) { deletePaymentCard(fundID); } @@ -220,6 +223,7 @@ function WalletPage() { resetSelectedPaymentMethodData, bankAccountList, lastUsedPaymentMethods, + personalPolicy, ]); /** diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage.tsx b/src/pages/workspace/ConnectExistingBusinessBankAccountPage.tsx similarity index 78% rename from src/pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage.tsx rename to src/pages/workspace/ConnectExistingBusinessBankAccountPage.tsx index b01d3bd26f1cb..a34bc943557dd 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsConnectExistingBankAccountPage.tsx +++ b/src/pages/workspace/ConnectExistingBusinessBankAccountPage.tsx @@ -11,49 +11,52 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {isBankAccountPartiallySetup} from '@libs/BankAccountUtils'; import Navigation from '@navigation/Navigation'; import type {PlatformStackScreenProps} from '@navigation/PlatformStackNavigation/types'; -import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import type {ConnectExistingBankAccountNavigatorParamList} from '@navigation/types'; import PaymentMethodList from '@pages/settings/Wallet/PaymentMethodList'; import type {PaymentMethodPressHandlerParams} from '@pages/settings/Wallet/WalletPage/types'; import {setWorkspaceReimbursement} from '@userActions/Policy/Policy'; import {navigateToBankAccountRoute} from '@userActions/ReimbursementAccount'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type WorkspaceWorkflowsConnectExistingBankAccountPageProps = PlatformStackScreenProps; +type ConnectExistingBusinessBankAccountPageProps = PlatformStackScreenProps; -function WorkspaceWorkflowsConnectExistingBankAccountPage({route}: WorkspaceWorkflowsConnectExistingBankAccountPageProps) { +function ConnectExistingBusinessBankAccountPage({route}: ConnectExistingBusinessBankAccountPageProps) { const icons = useMemoizedLazyExpensifyIcons(['ArrowRight']); const policyID = route.params?.policyID; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: false}); const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD, {canBeMissing: true}); const policyName = policy?.name ?? ''; + const policyCurrency = policy?.outputCurrency ?? ''; const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); const {translate} = useLocalize(); const handleAddBankAccountPress = () => { - navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + navigateToBankAccountRoute(policyID); }; const handleItemPress = ({methodID, accountData}: PaymentMethodPressHandlerParams) => { + if (policyID === undefined) { + return; + } const newReimburserEmail = policy?.achAccount?.reimburser ?? policy?.owner ?? ''; setWorkspaceReimbursement({ - policyID: route.params.policyID, + policyID, reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES, bankAccountID: methodID ?? CONST.DEFAULT_NUMBER_ID, reimburserEmail: newReimburserEmail, lastPaymentMethod: lastPaymentMethod?.[policyID], - shouldUpdateLastPaymentMethod: true, + shouldUpdateLastPaymentMethod: accountData?.state === CONST.BANK_ACCOUNT.STATE.OPEN, }); Navigation.setNavigationActionToMicrotaskQueue(() => { if (isBankAccountPartiallySetup(accountData?.state)) { - navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID)); + navigateToBankAccountRoute(route.params.policyID); } else { - Navigation.goBack(ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)); + Navigation.closeRHPFlow(); } }); }; @@ -61,7 +64,7 @@ function WorkspaceWorkflowsConnectExistingBankAccountPage({route}: WorkspaceWork return ( @@ -85,4 +88,6 @@ function WorkspaceWorkflowsConnectExistingBankAccountPage({route}: WorkspaceWork ); } -export default WorkspaceWorkflowsConnectExistingBankAccountPage; +ConnectExistingBusinessBankAccountPage.displayName = 'ConnectExistingBusinessBankAccountPage'; + +export default ConnectExistingBusinessBankAccountPage; diff --git a/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx b/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx index b835faacee518..e1b3ff550fdbf 100644 --- a/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx +++ b/src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx @@ -51,7 +51,7 @@ function WorkspaceOverviewCurrencyPage({policy}: WorkspaceOverviewCurrencyPagePr if (isCurrencySupportedForGlobalReimbursement(item.currencyCode as CurrencyType)) { const hasValidExistingAccounts = getEligibleExistingBusinessBankAccounts(bankAccountList, item.currencyCode, true).length > 0; if (hasValidExistingAccounts) { - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.getRoute(policy.id)); + Navigation.navigate(ROUTES.BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT.getRoute(policy.id)); return; } navigateToBankAccountRoute(policy.id, ROUTES.WORKSPACE_WORKFLOWS.getRoute(policy.id), {forceReplace: true}); diff --git a/src/pages/workspace/WorkspaceResetBankAccountModal.tsx b/src/pages/workspace/WorkspaceResetBankAccountModal.tsx index 678bdc025e054..ea398f371d113 100644 --- a/src/pages/workspace/WorkspaceResetBankAccountModal.tsx +++ b/src/pages/workspace/WorkspaceResetBankAccountModal.tsx @@ -72,7 +72,7 @@ function WorkspaceResetBankAccountModal({ setIsResettingBankAccount(true); } - resetNonUSDBankAccount(policyID, policy?.achAccount, achData?.bankAccountID); + resetNonUSDBankAccount(policyID, policy?.achAccount, achData?.bankAccountID, lastPaymentMethod); if (setShouldShowConnectedVerifiedBankAccount) { setShouldShowConnectedVerifiedBankAccount(false); diff --git a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx index 1e0ed32243cde..19ab531383dd5 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx @@ -11,6 +11,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePaymentMethodState from '@hooks/usePaymentMethodState'; import type {FormattedSelectedPaymentMethod} from '@hooks/usePaymentMethodState/types'; +import usePolicy from '@hooks/usePolicy'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -48,6 +49,8 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) const {translate} = useLocalize(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true}); + const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID, {canBeMissing: true}); + const personalPolicy = usePolicy(personalPolicyID); const {paymentMethod, setPaymentMethod, resetSelectedPaymentMethodData} = usePaymentMethodState(); const paymentMethodButtonRef = useRef(null); const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false); @@ -154,9 +157,9 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) const deletePaymentMethod = useCallback(() => { const bankAccountID = paymentMethod.selectedPaymentMethod.bankAccountID; if (paymentMethod.selectedPaymentMethodType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && bankAccountID) { - deletePaymentBankAccount(bankAccountID); + deletePaymentBankAccount(bankAccountID, undefined, undefined, personalPolicy); } - }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethodType]); + }, [paymentMethod.selectedPaymentMethod.bankAccountID, paymentMethod.selectedPaymentMethodType, personalPolicy]); const makeDefaultPaymentMethod = useCallback(() => { // Find the previous default payment method so we can revert if the MakeDefaultPaymentMethod command errors @@ -179,7 +182,7 @@ function WorkspaceInvoiceVBASection({policyID}: WorkspaceInvoiceVBASectionProps) } if (hasValidExistingAccounts && !shouldShowContinueModal) { - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.getRoute(policyID)); + Navigation.navigate(ROUTES.BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT.getRoute(policyID)); return; } navigateToBankAccountRoute(policyID, ROUTES.WORKSPACE_INVOICES.getRoute(policyID)); diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 206f9444d0027..6d4890d76faf9 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -359,7 +359,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) { return; } if (!shouldShowBankAccount && hasValidExistingAccounts && !shouldShowContinueModal) { - Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT.getRoute(route.params.policyID)); + Navigation.navigate(ROUTES.BANK_ACCOUNT_CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT.getRoute(route.params.policyID)); return; } navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID));