From f3a06bcfac6e01d3afb2fa7468f262179f337cfa Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Tue, 14 Jul 2026 16:31:56 -0700 Subject: [PATCH 01/11] Add travelBillingDepositOnly beta and travel settlement account helpers --- src/CONST/index.ts | 1 + src/libs/CardUtils.ts | 23 +++++++++++++ src/libs/TravelInvoicingUtils.ts | 8 +++++ src/types/onyx/ExpensifyCardSettings.ts | 6 ++++ tests/unit/CardUtilsTest.ts | 43 +++++++++++++++++++++++++ tests/unit/TravelInvoicingUtilsTest.ts | 26 +++++++++++++++ 6 files changed, 107 insertions(+) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 0429be45be59..bbc65ce3217e 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -977,6 +977,7 @@ const CONST = { CUSTOM_AGENT: 'customAgent', DEFAULT_ROOMS: 'defaultRooms', PREVENT_SPOTNANA_TRAVEL: 'preventSpotnanaTravel', + TRAVEL_BILLING_DEPOSIT_ONLY: 'travelBillingDepositOnly', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', NETSUITE_USA_TAX: 'netsuiteUsaTax', PER_DIEM: 'newDotPerDiem', diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 4130d4efb9e7..ae4130d88a66 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 []; @@ -2019,6 +2040,8 @@ export { getTranslationKeyForCardStatus, maskPin, getEligibleBankAccountsForCard, + getEligibleBankAccountsForTravelInvoicing, + isDepositOnlyBankAccount, sortCardsByCardholderName, isCurrencySupportedForECards, getCardFeedIcon, diff --git a/src/libs/TravelInvoicingUtils.ts b/src/libs/TravelInvoicingUtils.ts index c29645794b98..c31fab1bca38 100644 --- a/src/libs/TravelInvoicingUtils.ts +++ b/src/libs/TravelInvoicingUtils.ts @@ -43,6 +43,13 @@ function getIsTravelInvoicingEnabled(cardSettings: ExpensifyCardSettingsBase | u return false; } +/** + * Checks whether the workspace pays its Travel Invoicing settlement by invoice (wire) instead of an ACH debit. + */ +function getIsTravelBillingPayByInvoice(cardSettings: ExpensifyCardSettingsBase | undefined): boolean { + return typeof cardSettings?.invoiceTo === 'string' && cardSettings.invoiceTo.length > 0; +} + /** * Checks if a settlement account is configured for Travel Invoicing. */ @@ -178,6 +185,7 @@ function isTravelCVVEligible(cardList: OnyxEntry): boolean { export { getIsTravelInvoicingEnabled, + getIsTravelBillingPayByInvoice, hasTravelInvoicingSettlementAccount, hasOutstandingTravelBalance, getTravelLimit, diff --git a/src/types/onyx/ExpensifyCardSettings.ts b/src/types/onyx/ExpensifyCardSettings.ts index 552b7c1bfdbe..685b8bce5b9e 100644 --- a/src/types/onyx/ExpensifyCardSettings.ts +++ b/src/types/onyx/ExpensifyCardSettings.ts @@ -83,6 +83,12 @@ type ExpensifyCardSettingsBase = { /** Amount (in cents) of in-flight settlement that has been billed but not yet settled at the bank */ pendingSettlementAmount?: number; + + /** Recipient of the travel settlement invoice; non-empty when the workspace pays by invoice instead of an ACH debit */ + invoiceTo?: string; + + /** Additional recipients who receive a copy of the travel settlement invoice */ + shareWith?: string[]; }; /** Spend rule filter condition */ diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 99443c289441..02f31e3d7d81 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -41,6 +41,7 @@ import { getDisplayableExpensifyCards, getDisplayableThirdPartyCards, getEligibleBankAccountsForCard, + getEligibleBankAccountsForTravelInvoicing, getEligibleBankAccountsForUkEuCard, getFeedNameForDisplay, getFeedType, @@ -4402,6 +4403,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 = { diff --git a/tests/unit/TravelInvoicingUtilsTest.ts b/tests/unit/TravelInvoicingUtilsTest.ts index bb7c39d34468..8d729ebc4891 100644 --- a/tests/unit/TravelInvoicingUtilsTest.ts +++ b/tests/unit/TravelInvoicingUtilsTest.ts @@ -1,5 +1,6 @@ import CONST from '@src/CONST'; import { + getIsTravelBillingPayByInvoice, getIsTravelInvoicingEnabled, getTravelInvoicingCard, getTravelLimit, @@ -62,6 +63,31 @@ describe('TravelInvoicingUtils', () => { }); }); + describe('getIsTravelBillingPayByInvoice', () => { + it('Should return false when travelSettings is undefined', () => { + const result = getIsTravelBillingPayByInvoice(undefined); + expect(result).toBe(false); + }); + + it('Should return false when invoiceTo is not set', () => { + const travelSettings = {isEnabled: true} as ExpensifyCardSettingsBase; + const result = getIsTravelBillingPayByInvoice(travelSettings); + expect(result).toBe(false); + }); + + it('Should return false when invoiceTo is an empty string', () => { + const travelSettings = {invoiceTo: ''} as ExpensifyCardSettingsBase; + const result = getIsTravelBillingPayByInvoice(travelSettings); + expect(result).toBe(false); + }); + + it('Should return true when invoiceTo is a non-empty string', () => { + const travelSettings = {invoiceTo: 'billing@example.com'} as ExpensifyCardSettingsBase; + const result = getIsTravelBillingPayByInvoice(travelSettings); + expect(result).toBe(true); + }); + }); + describe('hasTravelInvoicingSettlementAccount', () => { it('Should return false when travelSettings is undefined', () => { const result = hasTravelInvoicingSettlementAccount(undefined); From 6acaf3eb1ec07b64f4f003c1d89206b90ecf8c19 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Tue, 14 Jul 2026 16:31:58 -0700 Subject: [PATCH 02/11] Show deposit-only settlement accounts and send-invoice copy for CTB --- src/languages/en.ts | 6 ++++ src/languages/es.ts | 6 ++++ .../WorkspaceTravelInvoicingSection.tsx | 29 +++++++++++++++---- ...ceTravelInvoicingSettlementAccountPage.tsx | 12 ++++++-- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 216a3d60c119..930a31a7327d 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6075,6 +6075,7 @@ const translations = { currentTravelSpendLabel: 'Current travel spend', currentTravelSpendPaymentQueued: (amount: string) => `Payment of ${amount} is queued and will be processed soon.`, currentTravelSpendCta: 'Pay balance', + sendInvoiceNowCta: 'Send invoice now', currentTravelLimitLabel: 'Current travel limit', settlementAccountLabel: 'Settlement account', settlementFrequencyLabel: 'Settlement frequency', @@ -6101,12 +6102,17 @@ const translations = { title: (amount: string) => `Pay balance of ${amount}?`, body: 'The payment will be queued and processed shortly after. This action cannot be undone once started.', }, + sendInvoiceModal: { + title: (amount: string) => `Send invoice for ${amount}?`, + body: "We'll create an invoice for your current travel spend. Your travel limit is freed up once the invoice is paid.", + }, exportToPDF: 'Export to PDF', exportToCSV: 'Export to CSV', selectDateRangeError: 'Please select a date range to export', 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/languages/es.ts b/src/languages/es.ts index 9bf1359733ee..81733f0ce8bd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5824,6 +5824,7 @@ ${amount} para ${merchant} - ${date}`, currentTravelSpendLabel: 'Gasto actual en viajes', currentTravelSpendPaymentQueued: (amount: string) => `El pago de ${amount} está en cola y se procesará pronto.`, currentTravelSpendCta: 'Pagar saldo', + sendInvoiceNowCta: 'Enviar factura ahora', currentTravelLimitLabel: 'Límite actual de viajes', settlementAccountLabel: 'Cuenta de liquidación', settlementFrequencyLabel: 'Frecuencia de liquidación', @@ -5851,12 +5852,17 @@ ${amount} para ${merchant} - ${date}`, title: (amount: string) => `¿Pagar el saldo de ${amount}?`, body: 'El pago se pondrá en cola y se procesará poco después. Esta acción no se puede deshacer una vez iniciada.', }, + sendInvoiceModal: { + title: (amount: string) => `¿Enviar factura por ${amount}?`, + body: 'Crearemos una factura por tu gasto de viaje actual. Tu límite de viaje se libera una vez pagada la factura.', + }, exportToPDF: 'Exportar a PDF', exportToCSV: 'Exportar a CSV', selectDateRangeError: 'Por favor, selecciona un rango de fechas para exportar', invalidDateRangeError: 'La fecha de inicio debe ser anterior a la fecha de fin', enabled: '¡Facturación de viajes consolidada habilitada!', enabledDescription: 'Todos los gastos de viaje de este espacio de trabajo ahora se centralizarán en una factura mensual.', + depositOnly: 'Solo depósito', }, personalDetailsDescription: 'Para poder reservar el viaje, por favor ingrese su nombre legal tal como aparece en su identificación oficial emitida por el gobierno.', }, diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx index 7936cd349bf3..7c76d8667ef2 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx @@ -12,6 +12,7 @@ import {useCurrencyListActions} from '@hooks/useCurrencyList'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import usePermissions from '@hooks/usePermissions'; import usePolicyFeatureWriteAccess from '@hooks/usePolicyFeatureWriteAccess'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; @@ -27,12 +28,13 @@ 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'; import {hasInProgressUSDVBBA} from '@libs/ReimbursementAccountUtils'; import { + getIsTravelBillingPayByInvoice, getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey, getTravelLimit, @@ -72,6 +74,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec const {isOffline} = useNetwork(); const {translate} = useLocalize(); const {convertToDisplayString} = useCurrencyListActions(); + const {isBetaEnabled} = usePermissions(); const workspaceAccountID = useWorkspaceAccountID(policyID); const {showConfirmModal, closeModal} = useConfirmModal(); @@ -114,6 +117,20 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec const shouldShowPayButton = travelSpend > 0 && isMonthlySettlementFrequency && !hasPendingSettlement; const formattedSpend = convertToDisplayString(travelSpend, CONST.CURRENCY.USD); + // Pay-by-invoice customers settle by wire against an invoice, so the pay CTA and modal use invoice copy + const isPayByInvoice = getIsTravelBillingPayByInvoice(travelSettings); + const payBalanceCtaText = translate( + isPayByInvoice + ? 'workspace.moreFeatures.travel.travelInvoicing.travelInvoicingSection.subsections.sendInvoiceNowCta' + : 'workspace.moreFeatures.travel.travelInvoicing.travelInvoicingSection.subsections.currentTravelSpendCta', + ); + const payBalanceModalTitle = isPayByInvoice + ? translate('workspace.moreFeatures.travel.travelInvoicing.sendInvoiceModal.title', formattedSpend) + : translate('workspace.moreFeatures.travel.travelInvoicing.payBalanceModal.title', formattedSpend); + const payBalanceModalBody = translate( + isPayByInvoice ? 'workspace.moreFeatures.travel.travelInvoicing.sendInvoiceModal.body' : 'workspace.moreFeatures.travel.travelInvoicing.payBalanceModal.body', + ); + // The pending settlement amount for the "payment queued" subtitle const formattedQueuedAmount = convertToDisplayString(pendingSettlementAmount, CONST.CURRENCY.USD); const formattedLimit = convertToDisplayString(travelLimit, CONST.CURRENCY.USD); @@ -147,7 +164,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec // Bank account eligibility for toggle handler const isSetupUnfinished = hasInProgressUSDVBBA(reimbursementAccount?.achData); - const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountList); + const eligibleBankAccounts = getEligibleBankAccountsForTravelInvoicing(bankAccountList, isBetaEnabled(CONST.BETAS.TRAVEL_BILLING_DEPOSIT_ONLY)); // Determine if Travel Invoicing is enabled based on isEnabled field const isTravelInvoicingEnabled = getIsTravelInvoicingEnabled(travelSettings); @@ -333,7 +350,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec {shouldShowPayButton && canWriteMoreFeatures && ( )} From 9fdd87a8b64c21abcc550af7dc1d7bcb83414bba Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Thu, 23 Jul 2026 17:31:38 -0700 Subject: [PATCH 08/11] Show invoice-specific copy when queueing an early travel bill for pay-by-invoice workspaces Queuing an early bill via "Send invoice now" reused the same "Payment is queued" copy as ACH settlement, which is misleading since no automatic debit happens for pay-by-invoice customers. --- src/languages/de.ts | 1 + src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + .../workspace/travel/WorkspaceTravelInvoicingSection.tsx | 4 +++- 11 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 4b9c1dcc66b2..85f68ad8b9c8 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6071,6 +6071,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU subsections: { currentTravelSpendLabel: 'Aktuelle Reisekosten', currentTravelSpendPaymentQueued: (amount: string) => `Die Zahlung über ${amount} ist in der Warteschlange und wird in Kürze bearbeitet.`, + currentTravelSpendInvoiceQueued: 'Eine neue Rechnung für Ihre Reisekosten wird erstellt und Ihnen in Kürze zugesendet.', currentTravelSpendInvoicePending: (amount: string) => `Eine Rechnung über ${amount} wurde gesendet und wartet auf Zahlung.`, currentTravelSpendCta: 'Saldo bezahlen', currentTravelLimitLabel: 'Aktuelles Reisekontingent', diff --git a/src/languages/en.ts b/src/languages/en.ts index 2add4d1363cb..6451ae333ee5 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6149,6 +6149,7 @@ const translations = { subsections: { currentTravelSpendLabel: 'Current travel spend', currentTravelSpendPaymentQueued: (amount: string) => `Payment of ${amount} is queued and will be processed soon.`, + currentTravelSpendInvoiceQueued: 'A new invoice for your travel spend will be created and sent to you soon.', currentTravelSpendInvoicePending: (amount: string) => `An invoice for ${amount} has been sent and is awaiting payment.`, currentTravelSpendCta: 'Pay balance', sendInvoiceNowCta: 'Send invoice now', diff --git a/src/languages/es.ts b/src/languages/es.ts index 8affdd1a5c95..c3e4a8892a0b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5904,6 +5904,7 @@ ${amount} para ${merchant} - ${date}`, subsections: { currentTravelSpendLabel: 'Gasto actual en viajes', currentTravelSpendPaymentQueued: (amount: string) => `El pago de ${amount} está en cola y se procesará pronto.`, + currentTravelSpendInvoiceQueued: 'Se creará y enviará pronto una nueva factura por tu gasto de viaje.', currentTravelSpendInvoicePending: (amount: string) => `Se ha enviado una factura por ${amount} y está a la espera de pago.`, currentTravelSpendCta: 'Pagar saldo', sendInvoiceNowCta: 'Enviar factura ahora', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index fdd59136dcca..c769f19964ef 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6091,6 +6091,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. subsections: { currentTravelSpendLabel: 'Dépenses de voyage actuelles', currentTravelSpendPaymentQueued: (amount: string) => `Le paiement de ${amount} est en file d’attente et sera traité bientôt.`, + currentTravelSpendInvoiceQueued: 'Une nouvelle facture pour vos dépenses de voyage sera créée et vous sera envoyée bientôt.', currentTravelSpendInvoicePending: (amount: string) => `Une facture de ${amount} a été envoyée et est en attente de paiement.`, currentTravelSpendCta: 'Payer le solde', currentTravelLimitLabel: 'Plafond de déplacement actuel', diff --git a/src/languages/it.ts b/src/languages/it.ts index 8d6fb97ed1e1..9e21cf9c7515 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6053,6 +6053,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. subsections: { currentTravelSpendLabel: 'Spesa di viaggio attuale', currentTravelSpendPaymentQueued: (amount: string) => `Il pagamento di ${amount} è in coda e verrà elaborato a breve.`, + currentTravelSpendInvoiceQueued: 'Una nuova fattura per la tua spesa di viaggio verrà creata e inviata a breve.', currentTravelSpendInvoicePending: (amount: string) => `Una fattura di ${amount} è stata inviata ed è in attesa di pagamento.`, currentTravelSpendCta: 'Paga saldo', currentTravelLimitLabel: 'Limite di viaggio attuale', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 921025fe8e59..92e886807eb1 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -5982,6 +5982,7 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO subsections: { currentTravelSpendLabel: '現在の出張費支出', currentTravelSpendPaymentQueued: (amount: string) => `${amount} の支払いはキューに登録されており、まもなく処理されます。`, + currentTravelSpendInvoiceQueued: '出張費用の新しい請求書がまもなく作成され、送付されます。', currentTravelSpendInvoicePending: (amount: string) => `${amount} の請求書が送信され、支払い待ちです。`, currentTravelSpendCta: '残高を支払う', currentTravelLimitLabel: '現在の出張上限', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index fd09f066045a..4eec5156fa5e 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6042,6 +6042,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ subsections: { currentTravelSpendLabel: 'Huidige reiskosten', currentTravelSpendPaymentQueued: (amount: string) => `Betaling van ${amount} staat in de wachtrij en wordt binnenkort verwerkt.`, + currentTravelSpendInvoiceQueued: 'Er wordt binnenkort een nieuwe factuur voor uw reisuitgaven aangemaakt en naar u verzonden.', currentTravelSpendInvoicePending: (amount: string) => `Er is een factuur voor ${amount} verzonden en deze wacht op betaling.`, currentTravelSpendCta: 'Saldo betalen', currentTravelLimitLabel: 'Huidige reisl imiet', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index f8e14073207c..ed764b99d876 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6021,6 +6021,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy subsections: { currentTravelSpendLabel: 'Aktualne wydatki na podróże', currentTravelSpendPaymentQueued: (amount: string) => `Płatność w wysokości ${amount} jest w kolejce i wkrótce zostanie przetworzona.`, + currentTravelSpendInvoiceQueued: 'Wkrótce zostanie utworzona i wysłana do Ciebie nowa faktura za wydatki podróżne.', currentTravelSpendInvoicePending: (amount: string) => `Faktura na kwotę ${amount} została wysłana i oczekuje na płatność.`, currentTravelSpendCta: 'Spłać saldo', currentTravelLimitLabel: 'Aktualny limit podróży', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f48fc1ac1bcb..431b5426f13c 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6034,6 +6034,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS subsections: { currentTravelSpendLabel: 'Gasto atual com viagens', currentTravelSpendPaymentQueued: (amount: string) => `O pagamento de ${amount} está na fila e será processado em breve.`, + currentTravelSpendInvoiceQueued: 'Uma nova fatura para o seu gasto de viagem será criada e enviada a você em breve.', currentTravelSpendInvoicePending: (amount: string) => `Uma fatura de ${amount} foi enviada e está aguardando pagamento.`, currentTravelSpendCta: 'Pagar saldo', currentTravelLimitLabel: 'Limite de viagem atual', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index d604c7f91ce2..20533d30b57c 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5854,6 +5854,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM subsections: { currentTravelSpendLabel: '当前差旅支出', currentTravelSpendPaymentQueued: (amount: string) => `金额为 ${amount} 的付款已排队,稍后将被处理。`, + currentTravelSpendInvoiceQueued: '您差旅支出的新发票即将生成并发送给您。', currentTravelSpendInvoicePending: (amount: string) => `金额为 ${amount} 的发票已发送,正在等待付款。`, currentTravelSpendCta: '支付余额', currentTravelLimitLabel: '当前出行限额', diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx index bd2924e094c3..ad4334ffb19d 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx @@ -349,7 +349,9 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec /> {hasPendingSettlement && ( - {translate('workspace.moreFeatures.travel.travelInvoicing.travelInvoicingSection.subsections.currentTravelSpendPaymentQueued', formattedQueuedAmount)} + {isPayByInvoice + ? translate('workspace.moreFeatures.travel.travelInvoicing.travelInvoicingSection.subsections.currentTravelSpendInvoiceQueued') + : translate('workspace.moreFeatures.travel.travelInvoicing.travelInvoicingSection.subsections.currentTravelSpendPaymentQueued', formattedQueuedAmount)} )} {hasPendingInvoice && ( From f4e95959b215c332f79556461b366196845ce851 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Thu, 23 Jul 2026 17:33:37 -0700 Subject: [PATCH 09/11] Revert hand-written translations, keep only en.ts for new key Translation bot handles non-English locale files; only en.ts should be edited by hand. --- src/languages/de.ts | 1 - src/languages/es.ts | 1 - src/languages/fr.ts | 1 - src/languages/it.ts | 1 - src/languages/ja.ts | 1 - src/languages/nl.ts | 1 - src/languages/pl.ts | 1 - src/languages/pt-BR.ts | 1 - src/languages/zh-hans.ts | 1 - 9 files changed, 9 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 85f68ad8b9c8..4b9c1dcc66b2 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6071,7 +6071,6 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU subsections: { currentTravelSpendLabel: 'Aktuelle Reisekosten', currentTravelSpendPaymentQueued: (amount: string) => `Die Zahlung über ${amount} ist in der Warteschlange und wird in Kürze bearbeitet.`, - currentTravelSpendInvoiceQueued: 'Eine neue Rechnung für Ihre Reisekosten wird erstellt und Ihnen in Kürze zugesendet.', currentTravelSpendInvoicePending: (amount: string) => `Eine Rechnung über ${amount} wurde gesendet und wartet auf Zahlung.`, currentTravelSpendCta: 'Saldo bezahlen', currentTravelLimitLabel: 'Aktuelles Reisekontingent', diff --git a/src/languages/es.ts b/src/languages/es.ts index c3e4a8892a0b..8affdd1a5c95 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5904,7 +5904,6 @@ ${amount} para ${merchant} - ${date}`, subsections: { currentTravelSpendLabel: 'Gasto actual en viajes', currentTravelSpendPaymentQueued: (amount: string) => `El pago de ${amount} está en cola y se procesará pronto.`, - currentTravelSpendInvoiceQueued: 'Se creará y enviará pronto una nueva factura por tu gasto de viaje.', currentTravelSpendInvoicePending: (amount: string) => `Se ha enviado una factura por ${amount} y está a la espera de pago.`, currentTravelSpendCta: 'Pagar saldo', sendInvoiceNowCta: 'Enviar factura ahora', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index c769f19964ef..fdd59136dcca 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6091,7 +6091,6 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. subsections: { currentTravelSpendLabel: 'Dépenses de voyage actuelles', currentTravelSpendPaymentQueued: (amount: string) => `Le paiement de ${amount} est en file d’attente et sera traité bientôt.`, - currentTravelSpendInvoiceQueued: 'Une nouvelle facture pour vos dépenses de voyage sera créée et vous sera envoyée bientôt.', currentTravelSpendInvoicePending: (amount: string) => `Une facture de ${amount} a été envoyée et est en attente de paiement.`, currentTravelSpendCta: 'Payer le solde', currentTravelLimitLabel: 'Plafond de déplacement actuel', diff --git a/src/languages/it.ts b/src/languages/it.ts index 9e21cf9c7515..8d6fb97ed1e1 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6053,7 +6053,6 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. subsections: { currentTravelSpendLabel: 'Spesa di viaggio attuale', currentTravelSpendPaymentQueued: (amount: string) => `Il pagamento di ${amount} è in coda e verrà elaborato a breve.`, - currentTravelSpendInvoiceQueued: 'Una nuova fattura per la tua spesa di viaggio verrà creata e inviata a breve.', currentTravelSpendInvoicePending: (amount: string) => `Una fattura di ${amount} è stata inviata ed è in attesa di pagamento.`, currentTravelSpendCta: 'Paga saldo', currentTravelLimitLabel: 'Limite di viaggio attuale', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 92e886807eb1..921025fe8e59 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -5982,7 +5982,6 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO subsections: { currentTravelSpendLabel: '現在の出張費支出', currentTravelSpendPaymentQueued: (amount: string) => `${amount} の支払いはキューに登録されており、まもなく処理されます。`, - currentTravelSpendInvoiceQueued: '出張費用の新しい請求書がまもなく作成され、送付されます。', currentTravelSpendInvoicePending: (amount: string) => `${amount} の請求書が送信され、支払い待ちです。`, currentTravelSpendCta: '残高を支払う', currentTravelLimitLabel: '現在の出張上限', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 4eec5156fa5e..fd09f066045a 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6042,7 +6042,6 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ subsections: { currentTravelSpendLabel: 'Huidige reiskosten', currentTravelSpendPaymentQueued: (amount: string) => `Betaling van ${amount} staat in de wachtrij en wordt binnenkort verwerkt.`, - currentTravelSpendInvoiceQueued: 'Er wordt binnenkort een nieuwe factuur voor uw reisuitgaven aangemaakt en naar u verzonden.', currentTravelSpendInvoicePending: (amount: string) => `Er is een factuur voor ${amount} verzonden en deze wacht op betaling.`, currentTravelSpendCta: 'Saldo betalen', currentTravelLimitLabel: 'Huidige reisl imiet', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index ed764b99d876..f8e14073207c 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6021,7 +6021,6 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy subsections: { currentTravelSpendLabel: 'Aktualne wydatki na podróże', currentTravelSpendPaymentQueued: (amount: string) => `Płatność w wysokości ${amount} jest w kolejce i wkrótce zostanie przetworzona.`, - currentTravelSpendInvoiceQueued: 'Wkrótce zostanie utworzona i wysłana do Ciebie nowa faktura za wydatki podróżne.', currentTravelSpendInvoicePending: (amount: string) => `Faktura na kwotę ${amount} została wysłana i oczekuje na płatność.`, currentTravelSpendCta: 'Spłać saldo', currentTravelLimitLabel: 'Aktualny limit podróży', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 431b5426f13c..f48fc1ac1bcb 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6034,7 +6034,6 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS subsections: { currentTravelSpendLabel: 'Gasto atual com viagens', currentTravelSpendPaymentQueued: (amount: string) => `O pagamento de ${amount} está na fila e será processado em breve.`, - currentTravelSpendInvoiceQueued: 'Uma nova fatura para o seu gasto de viagem será criada e enviada a você em breve.', currentTravelSpendInvoicePending: (amount: string) => `Uma fatura de ${amount} foi enviada e está aguardando pagamento.`, currentTravelSpendCta: 'Pagar saldo', currentTravelLimitLabel: 'Limite de viagem atual', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 20533d30b57c..d604c7f91ce2 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5854,7 +5854,6 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM subsections: { currentTravelSpendLabel: '当前差旅支出', currentTravelSpendPaymentQueued: (amount: string) => `金额为 ${amount} 的付款已排队,稍后将被处理。`, - currentTravelSpendInvoiceQueued: '您差旅支出的新发票即将生成并发送给您。', currentTravelSpendInvoicePending: (amount: string) => `金额为 ${amount} 的发票已发送,正在等待付款。`, currentTravelSpendCta: '支付余额', currentTravelLimitLabel: '当前出行限额', From 765cad81617826ca08ead6226f90e5c988f0cf78 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Thu, 23 Jul 2026 17:56:06 -0700 Subject: [PATCH 10/11] Apply Polyglot Parrot suggested translations for travel invoicing copy --- src/languages/de.ts | 7 ++++--- src/languages/es.ts | 3 ++- src/languages/fr.ts | 5 +++-- src/languages/it.ts | 1 + src/languages/ja.ts | 5 +++-- src/languages/nl.ts | 5 +++-- src/languages/pl.ts | 5 +++-- src/languages/pt-BR.ts | 3 ++- src/languages/zh-hans.ts | 5 +++-- 9 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 4b9c1dcc66b2..30d9ad5b54bc 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6071,7 +6071,8 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU subsections: { currentTravelSpendLabel: 'Aktuelle Reisekosten', currentTravelSpendPaymentQueued: (amount: string) => `Die Zahlung über ${amount} ist in der Warteschlange und wird in Kürze bearbeitet.`, - currentTravelSpendInvoicePending: (amount: string) => `Eine Rechnung über ${amount} wurde gesendet und wartet auf Zahlung.`, + currentTravelSpendInvoiceQueued: 'Eine neue Rechnung für Ihre Reisekosten wird erstellt und Ihnen in Kürze zugesandt.', + currentTravelSpendInvoicePending: (amount: string) => `Eine Rechnung über ${amount} wurde gesendet und wartet auf Bezahlung.`, currentTravelSpendCta: 'Saldo bezahlen', currentTravelLimitLabel: 'Aktuelles Reisekontingent', settlementAccountLabel: 'Verrechnungskonto', @@ -6109,9 +6110,9 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU enabledDescription: 'Alle Reisekosten in diesem Workspace werden nun in einer monatlichen Rechnung zentralisiert.', sendInvoiceModal: { title: (amount: string) => `Rechnung über ${amount} senden?`, - body: 'Wir erstellen eine Rechnung für Ihre aktuellen Reisekosten. Ihr Reisebudget wird wieder freigegeben, sobald die Rechnung bezahlt ist.', + body: 'Wir erstellen eine Rechnung für Ihre aktuellen Reisekosten. Ihr Reiselimit wird wieder frei, sobald die Rechnung bezahlt ist.', }, - depositOnly: 'Nur Einzahlung', + depositOnly: 'Nur Einzahlungen', }, personalDetailsDescription: 'Um eine Reise zu buchen, gib bitte deinen amtlichen Namen genau so ein, wie er auf deinem amtlichen Ausweis steht.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 8affdd1a5c95..8061810bca01 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5904,6 +5904,7 @@ ${amount} para ${merchant} - ${date}`, subsections: { currentTravelSpendLabel: 'Gasto actual en viajes', currentTravelSpendPaymentQueued: (amount: string) => `El pago de ${amount} está en cola y se procesará pronto.`, + currentTravelSpendInvoiceQueued: 'Pronto se creará y enviará una nueva factura por tus gastos de viaje.', currentTravelSpendInvoicePending: (amount: string) => `Se ha enviado una factura por ${amount} y está a la espera de pago.`, currentTravelSpendCta: 'Pagar saldo', sendInvoiceNowCta: 'Enviar factura ahora', @@ -5936,7 +5937,7 @@ ${amount} para ${merchant} - ${date}`, }, sendInvoiceModal: { title: (amount: string) => `¿Enviar factura por ${amount}?`, - body: 'Crearemos una factura por tus gastos de viaje actuales. Tu límite de viaje se liberará una vez que se pague la factura.', + body: 'Crearemos una factura por tus gastos de viaje actuales. Tu límite de viaje se libera una vez que se pague la factura.', }, exportToPDF: 'Exportar a PDF', exportToCSV: 'Exportar a CSV', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index fdd59136dcca..4ae0e2884937 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6091,6 +6091,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. subsections: { currentTravelSpendLabel: 'Dépenses de voyage actuelles', currentTravelSpendPaymentQueued: (amount: string) => `Le paiement de ${amount} est en file d’attente et sera traité bientôt.`, + currentTravelSpendInvoiceQueued: 'Une nouvelle facture pour vos dépenses de voyage sera créée et vous sera envoyée bientôt.', currentTravelSpendInvoicePending: (amount: string) => `Une facture de ${amount} a été envoyée et est en attente de paiement.`, currentTravelSpendCta: 'Payer le solde', currentTravelLimitLabel: 'Plafond de déplacement actuel', @@ -6129,8 +6130,8 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. enabled: 'Facturation de voyage consolidée activée !', enabledDescription: 'Toutes les dépenses de voyage sur cet espace de travail seront désormais centralisées dans une facture mensuelle.', sendInvoiceModal: { - title: (amount: string) => `Envoyer la facture pour ${amount} ?`, - body: 'Nous créerons une facture pour vos dépenses de voyage actuelles. Votre plafond de voyage sera à nouveau disponible une fois la facture payée.', + title: (amount: string) => `Envoyer la facture de ${amount} ?`, + body: 'Nous créerons une facture pour vos dépenses de voyage actuelles. Votre plafond de voyage est à nouveau disponible une fois la facture payée.', }, depositOnly: 'Dépôt uniquement', }, diff --git a/src/languages/it.ts b/src/languages/it.ts index 8d6fb97ed1e1..11d9e866c6fc 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6053,6 +6053,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. subsections: { currentTravelSpendLabel: 'Spesa di viaggio attuale', currentTravelSpendPaymentQueued: (amount: string) => `Il pagamento di ${amount} è in coda e verrà elaborato a breve.`, + currentTravelSpendInvoiceQueued: 'Una nuova fattura per le tue spese di viaggio verrà creata e ti sarà inviata a breve.', currentTravelSpendInvoicePending: (amount: string) => `Una fattura di ${amount} è stata inviata ed è in attesa di pagamento.`, currentTravelSpendCta: 'Paga saldo', currentTravelLimitLabel: 'Limite di viaggio attuale', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 921025fe8e59..cdf13e0f50ac 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -5982,6 +5982,7 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO subsections: { currentTravelSpendLabel: '現在の出張費支出', currentTravelSpendPaymentQueued: (amount: string) => `${amount} の支払いはキューに登録されており、まもなく処理されます。`, + currentTravelSpendInvoiceQueued: '出張費用の新しい請求書が作成され、まもなくお客様に送信されます。', currentTravelSpendInvoicePending: (amount: string) => `${amount} の請求書が送信され、支払い待ちです。`, currentTravelSpendCta: '残高を支払う', currentTravelLimitLabel: '現在の出張上限', @@ -6014,10 +6015,10 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO enabled: '一括出張請求が有効になりました!', enabledDescription: 'このワークスペースでの出張費用は、今後すべて月次の請求書に集約されます。', sendInvoiceModal: { - title: (amount: string) => `${amount} の請求書を送信しますか?`, + title: (amount: string) => `${amount}の請求書を送信しますか?`, body: '現在の出張費用について請求書を作成します。請求書が支払われると、出張の利用可能枠が再び使えるようになります。', }, - depositOnly: '入金のみ', + depositOnly: '入金専用', }, personalDetailsDescription: '旅行を予約するために、政府発行の身分証明書に記載されているとおりの正式な氏名を入力してください。', }, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index fd09f066045a..fbc3d3357190 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6042,6 +6042,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ subsections: { currentTravelSpendLabel: 'Huidige reiskosten', currentTravelSpendPaymentQueued: (amount: string) => `Betaling van ${amount} staat in de wachtrij en wordt binnenkort verwerkt.`, + currentTravelSpendInvoiceQueued: 'Er wordt binnenkort een nieuwe factuur voor je reiskosten aangemaakt en naar je verzonden.', currentTravelSpendInvoicePending: (amount: string) => `Er is een factuur voor ${amount} verzonden en deze wacht op betaling.`, currentTravelSpendCta: 'Saldo betalen', currentTravelLimitLabel: 'Huidige reisl imiet', @@ -6054,7 +6055,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ reduceLimitWarning: 'Als u het limiet verlaagt, kunnen leden die dit bedrag al hebben overschreden geen nieuwe reisboekingen maken tot volgende maand.', provisioningError: 'We konden voor sommige leden van je werkruimte geen toegang instellen tot Geconsolideerde Reisfacturering. Probeer het later opnieuw of neem contact op met Concierge voor ondersteuning.', - sendInvoiceNowCta: 'Factuur nu verzenden', + sendInvoiceNowCta: 'Verzend factuur nu', }, }, disableModal: { @@ -6079,7 +6080,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ enabledDescription: 'Alle reiskosten in deze workspace worden nu gebundeld op één maandelijkse factuur.', sendInvoiceModal: { title: (amount: string) => `Factuur voor ${amount} versturen?`, - body: 'We maken een factuur voor je huidige reiskosten. Je reistegoed komt weer vrij zodra de factuur is betaald.', + body: 'We maken een factuur aan voor je huidige reiskosten. Je reistegoed komt weer vrij zodra de factuur is betaald.', }, depositOnly: 'Alleen storting', }, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index f8e14073207c..5f85d843b68e 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6021,6 +6021,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy subsections: { currentTravelSpendLabel: 'Aktualne wydatki na podróże', currentTravelSpendPaymentQueued: (amount: string) => `Płatność w wysokości ${amount} jest w kolejce i wkrótce zostanie przetworzona.`, + currentTravelSpendInvoiceQueued: 'Nowa faktura za twoje wydatki związane z podróżą zostanie wkrótce utworzona i wysłana do ciebie.', currentTravelSpendInvoicePending: (amount: string) => `Faktura na kwotę ${amount} została wysłana i oczekuje na płatność.`, currentTravelSpendCta: 'Spłać saldo', currentTravelLimitLabel: 'Aktualny limit podróży', @@ -6058,8 +6059,8 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy enabled: 'Włączono zbiorcze rozliczanie podróży!', enabledDescription: 'Wszystkie wydatki podróżne w tym obszarze roboczym będą teraz scentralizowane na miesięcznym rachunku.', sendInvoiceModal: { - title: (amount: string) => `Wysłać fakturę na kwotę ${amount}?`, - body: 'Utworzymy fakturę za twoje bieżące wydatki na podróż. Twój limit podróży zwolni się, gdy faktura zostanie opłacona.', + title: (amount: string) => `Wysłać fakturę na ${amount}?`, + body: 'Utworzymy fakturę za twoje bieżące wydatki na podróż. Twój limit podróży zostanie zwolniony, gdy faktura zostanie opłacona.', }, depositOnly: 'Tylko wpłata', }, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f48fc1ac1bcb..da8312a146fa 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6034,6 +6034,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS subsections: { currentTravelSpendLabel: 'Gasto atual com viagens', currentTravelSpendPaymentQueued: (amount: string) => `O pagamento de ${amount} está na fila e será processado em breve.`, + currentTravelSpendInvoiceQueued: 'Uma nova fatura dos seus gastos de viagem será criada e enviada para você em breve.', currentTravelSpendInvoicePending: (amount: string) => `Uma fatura de ${amount} foi enviada e está aguardando pagamento.`, currentTravelSpendCta: 'Pagar saldo', currentTravelLimitLabel: 'Limite de viagem atual', @@ -6072,7 +6073,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS enabledDescription: 'Todos os gastos de viagem neste workspace agora serão centralizados em uma fatura mensal.', sendInvoiceModal: { title: (amount: string) => `Enviar fatura de ${amount}?`, - body: 'Vamos criar uma fatura para seus gastos atuais de viagem. Seu limite de viagem é liberado assim que a fatura é paga.', + body: 'Vamos criar uma fatura para seus gastos atuais de viagem. Seu limite de viagem será liberado assim que a fatura for paga.', }, depositOnly: 'Apenas depósito', }, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index d604c7f91ce2..0fb5b49e812d 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5854,6 +5854,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM subsections: { currentTravelSpendLabel: '当前差旅支出', currentTravelSpendPaymentQueued: (amount: string) => `金额为 ${amount} 的付款已排队,稍后将被处理。`, + currentTravelSpendInvoiceQueued: '您的差旅费用新发票将很快创建并发送给您。', currentTravelSpendInvoicePending: (amount: string) => `金额为 ${amount} 的发票已发送,正在等待付款。`, currentTravelSpendCta: '支付余额', currentTravelLimitLabel: '当前出行限额', @@ -5877,8 +5878,8 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM invalidDateRangeError: '开始日期必须早于结束日期', enabled: '已启用合并差旅账单!', enabledDescription: '此工作区的所有差旅支出现在将统一汇总到一份月度账单中。', - sendInvoiceModal: {title: (amount: string) => `发送金额为 ${amount} 的发票?`, body: '我们会根据你当前的差旅支出创建一张发票。发票支付后,你的差旅额度将被释放。'}, - depositOnly: '仅限存款', + sendInvoiceModal: {title: (amount: string) => `要发送金额为 ${amount} 的发票吗?`, body: '我们会为您当前的差旅支出创建一张发票。发票付清后,您的差旅额度将被释放。'}, + depositOnly: '仅存款', }, personalDetailsDescription: '为预订行程,请输入您在政府签发的身份证件上显示的法定姓名。', }, From b482a26c58296e9658c43ee4999ca86b4fb20986 Mon Sep 17 00:00:00 2001 From: Ben Limpich Date: Fri, 24 Jul 2026 10:41:30 -0700 Subject: [PATCH 11/11] Remove deposit-only settlement account handling (split into its own PR) --- src/languages/de.ts | 1 - src/languages/en.ts | 1 - src/languages/es.ts | 1 - src/languages/fr.ts | 1 - src/languages/it.ts | 1 - src/languages/ja.ts | 1 - src/languages/nl.ts | 1 - src/languages/pl.ts | 1 - src/languages/pt-BR.ts | 1 - src/languages/zh-hans.ts | 1 - src/libs/CardUtils.ts | 23 ---------- .../WorkspaceTravelInvoicingSection.tsx | 4 +- ...ceTravelInvoicingSettlementAccountPage.tsx | 12 ++---- tests/unit/CardUtilsTest.ts | 43 ------------------- 14 files changed, 6 insertions(+), 86 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 30d9ad5b54bc..4e027f285ef1 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6112,7 +6112,6 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU title: (amount: string) => `Rechnung über ${amount} senden?`, body: 'Wir erstellen eine Rechnung für Ihre aktuellen Reisekosten. Ihr Reiselimit wird wieder frei, sobald die Rechnung bezahlt ist.', }, - depositOnly: 'Nur Einzahlungen', }, personalDetailsDescription: 'Um eine Reise zu buchen, gib bitte deinen amtlichen Namen genau so ein, wie er auf deinem amtlichen Ausweis steht.', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 6451ae333ee5..1fa0f92a868b 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6189,7 +6189,6 @@ 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/languages/es.ts b/src/languages/es.ts index 8061810bca01..502dd9cc8145 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5945,7 +5945,6 @@ ${amount} para ${merchant} - ${date}`, invalidDateRangeError: 'La fecha de inicio debe ser anterior a la fecha de fin', enabled: '¡Facturación de viajes consolidada habilitada!', enabledDescription: 'Todos los gastos de viaje de este espacio de trabajo ahora se centralizarán en una factura mensual.', - depositOnly: 'Solo depósito', }, personalDetailsDescription: 'Para poder reservar el viaje, por favor ingrese su nombre legal tal como aparece en su identificación oficial emitida por el gobierno.', }, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 4ae0e2884937..cd89be3d8c1c 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6133,7 +6133,6 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. title: (amount: string) => `Envoyer la facture de ${amount} ?`, body: 'Nous créerons une facture pour vos dépenses de voyage actuelles. Votre plafond de voyage est à nouveau disponible une fois la facture payée.', }, - depositOnly: 'Dépôt uniquement', }, personalDetailsDescription: 'Pour pouvoir réserver un voyage, veuillez saisir votre nom légal tel qu’il apparaît sur votre pièce d’identité délivrée par le gouvernement.', }, diff --git a/src/languages/it.ts b/src/languages/it.ts index 11d9e866c6fc..61900046b49c 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6095,7 +6095,6 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. title: (amount: string) => `Inviare la fattura per ${amount}?`, body: 'Creeremo una fattura per le tue spese di viaggio attuali. Il tuo limite di viaggio si libera una volta che la fattura è stata pagata.', }, - depositOnly: 'Solo deposito', }, personalDetailsDescription: 'Per prenotare il viaggio, inserisci il tuo nome legale così come appare sul tuo documento d’identità rilasciato dal governo.', }, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index cdf13e0f50ac..207e8880d52f 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6018,7 +6018,6 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO title: (amount: string) => `${amount}の請求書を送信しますか?`, body: '現在の出張費用について請求書を作成します。請求書が支払われると、出張の利用可能枠が再び使えるようになります。', }, - depositOnly: '入金専用', }, personalDetailsDescription: '旅行を予約するために、政府発行の身分証明書に記載されているとおりの正式な氏名を入力してください。', }, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index fbc3d3357190..fb5e18fa62b3 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6082,7 +6082,6 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ title: (amount: string) => `Factuur voor ${amount} versturen?`, body: 'We maken een factuur aan voor je huidige reiskosten. Je reistegoed komt weer vrij zodra de factuur is betaald.', }, - depositOnly: 'Alleen storting', }, personalDetailsDescription: 'Om een reis te boeken, voer je wettelijke naam in zoals deze op je door de overheid uitgegeven identiteitsbewijs staat.', }, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 5f85d843b68e..48a4c51a71fd 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6062,7 +6062,6 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy title: (amount: string) => `Wysłać fakturę na ${amount}?`, body: 'Utworzymy fakturę za twoje bieżące wydatki na podróż. Twój limit podróży zostanie zwolniony, gdy faktura zostanie opłacona.', }, - depositOnly: 'Tylko wpłata', }, personalDetailsDescription: 'Aby zarezerwować podróż, wpisz swoje imię i nazwisko dokładnie tak, jak widnieje w Twoim dokumencie tożsamości wydanym przez organ państwowy.', }, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index da8312a146fa..a5373c4a60fb 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6075,7 +6075,6 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS title: (amount: string) => `Enviar fatura de ${amount}?`, body: 'Vamos criar uma fatura para seus gastos atuais de viagem. Seu limite de viagem será liberado assim que a fatura for paga.', }, - depositOnly: 'Apenas depósito', }, personalDetailsDescription: 'Para reservar viagens, insira seu nome legal exatamente como consta no documento de identificação emitido pelo governo.', }, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 0fb5b49e812d..e6510231cdd4 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5879,7 +5879,6 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM enabled: '已启用合并差旅账单!', enabledDescription: '此工作区的所有差旅支出现在将统一汇总到一份月度账单中。', sendInvoiceModal: {title: (amount: string) => `要发送金额为 ${amount} 的发票吗?`, body: '我们会为您当前的差旅支出创建一张发票。发票付清后,您的差旅额度将被释放。'}, - depositOnly: '仅存款', }, personalDetailsDescription: '为预订行程,请输入您在政府签发的身份证件上显示的法定姓名。', }, diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 2891fabdf405..a1cce79deae2 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -10,7 +10,6 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type { - BankAccount, BankAccountList, Card, CardFeeds, @@ -513,26 +512,6 @@ 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 []; @@ -2050,8 +2029,6 @@ 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 ad4334ffb19d..12540498aa9f 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, getEligibleBankAccountsForTravelInvoicing} from '@libs/CardUtils'; +import {getCardSettings, getEligibleBankAccountsForCard} from '@libs/CardUtils'; import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import Navigation from '@libs/Navigation/Navigation'; import {areTravelPersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; @@ -169,7 +169,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec // Bank account eligibility for toggle handler const isSetupUnfinished = hasInProgressUSDVBBA(reimbursementAccount?.achData); - const eligibleBankAccounts = getEligibleBankAccountsForTravelInvoicing(bankAccountList, isPayByInvoice); + const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountList); // 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 89246d46a3cb..016c732c29c0 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, getEligibleBankAccountsForTravelInvoicing, isDepositOnlyBankAccount} from '@libs/CardUtils'; +import {getCardSettings, getEligibleBankAccountsForCard} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import {getIsTravelBillingPayByInvoice, getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey} from '@libs/TravelInvoicingUtils'; +import {getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey} from '@libs/TravelInvoicingUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; @@ -48,8 +48,7 @@ function WorkspaceTravelInvoicingSettlementAccountPage({route}: WorkspaceTravelI const isSuccess = !!cardSettings?.isSuccess; const isTravelInvoicingEnabled = getIsTravelInvoicingEnabled(travelSettings); const paymentBankAccountID = travelSettings?.paymentBankAccountID; - const canUseDepositOnlyAccounts = getIsTravelBillingPayByInvoice(travelSettings); - const eligibleBankAccounts = getEligibleBankAccountsForTravelInvoicing(bankAccountsList, canUseDepositOnlyAccounts); + const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountsList); const getVerificationState = () => { if (cardOnWaitlist) { @@ -70,15 +69,12 @@ 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: isDepositOnlyBankAccount(bankAccount) - ? `${accountEndingIn} ${CONST.DOT_SEPARATOR} ${translate('workspace.moreFeatures.travel.travelInvoicing.depositOnly')}` - : accountEndingIn, + alternateText: `${translate('workspace.expensifyCard.accountEndingIn')} ${getLastFourDigits(bankAccountNumber)}`, keyForList: bankAccountID?.toString() ?? '', isSelected: bankAccountID === paymentBankAccountID, }; diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 4a62cf2c2006..9d7fab0ee8c0 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -42,7 +42,6 @@ import { getDisplayableThirdPartyCards, getDomainByFundID, getEligibleBankAccountsForCard, - getEligibleBankAccountsForTravelInvoicing, getEligibleBankAccountsForUkEuCard, getFeedNameForDisplay, getFeedType, @@ -4398,48 +4397,6 @@ 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 = {