diff --git a/src/languages/en.ts b/src/languages/en.ts index c0968a1c508f..6fe088788030 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6182,6 +6182,7 @@ const translations = { invalidDateRangeError: 'The start date must be before the end date', enabled: 'Consolidated Travel Billing enabled!', enabledDescription: 'All travel spend on this workspace will now be centralized in a monthly bill.', + depositOnly: 'Deposit only', }, personalDetailsDescription: 'In order to book travel, please enter your legal name as it appears on your government-issued ID.', }, diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index a1cce79deae2..2891fabdf405 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -10,6 +10,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type { + BankAccount, BankAccountList, Card, CardFeeds, @@ -512,6 +513,26 @@ function getEligibleBankAccountsForCard(bankAccountsList: OnyxEntry, canUseDepositOnlyAccounts: boolean) { + const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountsList); + if (!canUseDepositOnlyAccounts || !bankAccountsList) { + return eligibleBankAccounts; + } + const depositOnlyBankAccounts = Object.values(bankAccountsList).filter(isDepositOnlyBankAccount); + return [...eligibleBankAccounts, ...depositOnlyBankAccounts]; +} + function getConnectionBankAccountsForReconciliation(connections: OnyxEntry>, connectionName: PolicyConnectionName | undefined): Array<{id: string; name: string}> { if (!connections || !connectionName) { return []; @@ -2029,6 +2050,8 @@ export { getTranslationKeyForCardStatus, maskPin, getEligibleBankAccountsForCard, + getEligibleBankAccountsForTravelInvoicing, + isDepositOnlyBankAccount, sortCardsByCardholderName, isCurrencySupportedForECards, getCardFeedIcon, diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx index bec0f8ddf074..fd0681a1afde 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx @@ -27,7 +27,7 @@ import { retryTravelCardsProvisioning, } from '@libs/actions/TravelInvoicing'; import {getLastFourDigits} from '@libs/BankAccountUtils'; -import {getCardSettings, getEligibleBankAccountsForCard} from '@libs/CardUtils'; +import {getCardSettings, getEligibleBankAccountsForTravelInvoicing} from '@libs/CardUtils'; import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import Navigation from '@libs/Navigation/Navigation'; import {areTravelPersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; @@ -147,7 +147,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec // Bank account eligibility for toggle handler const isSetupUnfinished = hasInProgressUSDVBBA(reimbursementAccount?.achData); - const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountList); + const eligibleBankAccounts = getEligibleBankAccountsForTravelInvoicing(bankAccountList, isPayByInvoice); // Determine if Travel Invoicing is enabled based on isEnabled field const isTravelInvoicingEnabled = getIsTravelInvoicingEnabled(travelSettings); diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSettlementAccountPage.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSettlementAccountPage.tsx index 016c732c29c0..89246d46a3cb 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSettlementAccountPage.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSettlementAccountPage.tsx @@ -14,9 +14,9 @@ import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; import {configureTravelInvoicingForPolicy, setTravelInvoicingSettlementAccount} from '@libs/actions/TravelInvoicing'; import {getLastFourDigits} from '@libs/BankAccountUtils'; -import {getCardSettings, getEligibleBankAccountsForCard} from '@libs/CardUtils'; +import {getCardSettings, getEligibleBankAccountsForTravelInvoicing, isDepositOnlyBankAccount} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import {getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey} from '@libs/TravelInvoicingUtils'; +import {getIsTravelBillingPayByInvoice, getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey} from '@libs/TravelInvoicingUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -48,7 +48,8 @@ function WorkspaceTravelInvoicingSettlementAccountPage({route}: WorkspaceTravelI const isSuccess = !!cardSettings?.isSuccess; const isTravelInvoicingEnabled = getIsTravelInvoicingEnabled(travelSettings); const paymentBankAccountID = travelSettings?.paymentBankAccountID; - const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountsList); + const canUseDepositOnlyAccounts = getIsTravelBillingPayByInvoice(travelSettings); + const eligibleBankAccounts = getEligibleBankAccountsForTravelInvoicing(bankAccountsList, canUseDepositOnlyAccounts); const getVerificationState = () => { if (cardOnWaitlist) { @@ -69,12 +70,15 @@ function WorkspaceTravelInvoicingSettlementAccountPage({route}: WorkspaceTravelI const bankName = (bankAccount.accountData?.addressName ?? '') as BankName; const bankAccountNumber = bankAccount.accountData?.accountNumber ?? ''; const bankAccountID = bankAccount.accountData?.bankAccountID ?? bankAccount.methodID; + const accountEndingIn = `${translate('workspace.expensifyCard.accountEndingIn')} ${getLastFourDigits(bankAccountNumber)}`; return { value: bankAccountID, text: bankAccount.title, leftElement: , - alternateText: `${translate('workspace.expensifyCard.accountEndingIn')} ${getLastFourDigits(bankAccountNumber)}`, + alternateText: isDepositOnlyBankAccount(bankAccount) + ? `${accountEndingIn} ${CONST.DOT_SEPARATOR} ${translate('workspace.moreFeatures.travel.travelInvoicing.depositOnly')}` + : accountEndingIn, keyForList: bankAccountID?.toString() ?? '', isSelected: bankAccountID === paymentBankAccountID, }; diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 9d7fab0ee8c0..4a62cf2c2006 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -42,6 +42,7 @@ import { getDisplayableThirdPartyCards, getDomainByFundID, getEligibleBankAccountsForCard, + getEligibleBankAccountsForTravelInvoicing, getEligibleBankAccountsForUkEuCard, getFeedNameForDisplay, getFeedType, @@ -4397,6 +4398,48 @@ describe('getEligibleBankAccountsForCard', () => { }); }); +describe('getEligibleBankAccountsForTravelInvoicing', () => { + const debitBusinessAccount: BankAccountList = { + '1': { + accountData: {type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, allowDebit: true, state: CONST.BANK_ACCOUNT.STATE.OPEN}, + bankCurrency: 'USD', + bankCountry: 'US', + }, + }; + + const depositOnlyAccount: BankAccountList = { + '2': { + accountData: {type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, allowDebit: false, defaultCredit: true, state: CONST.BANK_ACCOUNT.STATE.OPEN}, + bankCurrency: 'USD', + bankCountry: 'US', + }, + }; + + const partiallySetupDepositOnlyAccount: BankAccountList = { + '3': { + accountData: {type: CONST.BANK_ACCOUNT.TYPE.BUSINESS, allowDebit: false, defaultCredit: true, state: CONST.BANK_ACCOUNT.STATE.SETUP}, + bankCurrency: 'USD', + bankCountry: 'US', + }, + }; + + it('excludes deposit-only accounts when deposit-only accounts are not allowed', () => { + const result = getEligibleBankAccountsForTravelInvoicing({...debitBusinessAccount, ...depositOnlyAccount}, false); + expect(result).toHaveLength(1); + expect(result.at(0)?.accountData?.allowDebit).toBe(true); + }); + + it('includes verified deposit-only accounts alongside debit-eligible accounts when allowed', () => { + const result = getEligibleBankAccountsForTravelInvoicing({...debitBusinessAccount, ...depositOnlyAccount}, true); + expect(result).toHaveLength(2); + }); + + it('always excludes partially set up deposit-only accounts', () => { + expect(getEligibleBankAccountsForTravelInvoicing(partiallySetupDepositOnlyAccount, true)).toHaveLength(0); + expect(getEligibleBankAccountsForTravelInvoicing(partiallySetupDepositOnlyAccount, false)).toHaveLength(0); + }); +}); + describe('getEligibleBankAccountsForUkEuCard', () => { it('excludes partially set up accounts', () => { const bankAccounts: BankAccountList = {