diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index b4db81c116559..5dac4a2ed5090 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -7933,7 +7933,7 @@ const CONST = {
* The Travel Invoicing feed type constant.
* This feed is used for Travel Invoicing cards which are separate from regular Expensify Cards.
*/
- PROGRAM_TRAVEL_US: 'TRAVEL_US',
+ PROGRAM_TRAVEL_US: 'PROGRAM_TRAVEL_US',
},
LAST_PAYMENT_METHOD: {
LAST_USED: 'lastUsed',
diff --git a/src/pages/workspace/travel/PolicyTravelPage.tsx b/src/pages/workspace/travel/PolicyTravelPage.tsx
index 42221a4b2d1e6..50f9e8260b053 100644
--- a/src/pages/workspace/travel/PolicyTravelPage.tsx
+++ b/src/pages/workspace/travel/PolicyTravelPage.tsx
@@ -72,11 +72,12 @@ function WorkspaceTravelPage({
const step = getTravelStep(policy, travelSettings, isBetaEnabled(CONST.BETAS.IS_TRAVEL_VERIFIED), policies, currentUserLogin);
const mainContent = (() => {
+ // TODO: Remove this conditional when Travel Invoicing feature is fully implemented
+ if (isTravelInvoicingEnabled) {
+ return ;
+ }
switch (step) {
case CONST.TRAVEL.STEPS.BOOK_OR_MANAGE_YOUR_TRIP:
- if (isTravelInvoicingEnabled) {
- return ;
- }
return ;
case CONST.TRAVEL.STEPS.REVIEWING_REQUEST:
return ;
diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx
index 5aa6eed4db2a0..6d9c69c3941e0 100644
--- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx
+++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx
@@ -1,4 +1,4 @@
-import React, {useState} from 'react';
+import React from 'react';
import {View} from 'react-native';
import AnimatedSubmitButton from '@components/AnimatedSubmitButton';
import MenuItem from '@components/MenuItem';
@@ -12,12 +12,13 @@ import useSingleExecution from '@hooks/useSingleExecution';
import useThemeStyles from '@hooks/useThemeStyles';
import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID';
import {openExternalLink} from '@libs/actions/Link';
-import {clearTravelInvoicingSettlementAccountErrors} from '@libs/actions/TravelInvoicing';
+import {clearTravelInvoicingSettlementAccountErrors, setTravelInvoicingSettlementAccount} from '@libs/actions/TravelInvoicing';
import {getLastFourDigits} from '@libs/BankAccountUtils';
+import {getEligibleBankAccountsForCard} from '@libs/CardUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
+import {hasInProgressUSDVBBA, REIMBURSEMENT_ACCOUNT_ROUTE_NAMES} from '@libs/ReimbursementAccountUtils';
import {
- getIsTravelInvoicingEnabled,
getTravelInvoicingCardSettingsKey,
getTravelLimit,
getTravelSettlementAccount,
@@ -30,7 +31,6 @@ import type {ToggleSettingOptionRowProps} from '@pages/workspace/workflows/Toggl
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import BookOrManageYourTrip from './BookOrManageYourTrip';
import CentralInvoicingLearnHow from './CentralInvoicingLearnHow';
import CentralInvoicingSubtitleWrapper from './CentralInvoicingSubtitleWrapper';
@@ -50,16 +50,15 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec
const {isExecuting, singleExecution} = useSingleExecution();
const icons = useMemoizedLazyExpensifyIcons(['LuggageWithLines', 'NewWindow']);
- const [isCentralInvoicingEnabled, setIsCentralInvoicingEnabled] = useState(true);
-
// For Travel Invoicing, we use a travel-specific card settings key
// The format is: private_expensifyCardSettings_{workspaceAccountID}_{feedType}
// where feedType is PROGRAM_TRAVEL_US for Travel Invoicing
const [cardSettings] = useOnyx(getTravelInvoicingCardSettingsKey(workspaceAccountID), {canBeMissing: true});
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST, {canBeMissing: true});
+ const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: true});
// Use pure selectors to derive state
- const isTravelInvoicingEnabled = getIsTravelInvoicingEnabled(cardSettings);
const hasSettlementAccount = hasTravelInvoicingSettlementAccount(cardSettings);
const travelSpend = getTravelSpend(cardSettings);
const travelLimit = getTravelLimit(cardSettings);
@@ -79,8 +78,41 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec
// Get any errors from the settlement account update
const hasSettlementAccountError = Object.keys(cardSettings?.errors ?? {}).length > 0;
+ // Bank account eligibility for toggle handler
+ const isSetupUnfinished = hasInProgressUSDVBBA(reimbursementAccount?.achData);
+ const eligibleBankAccounts = getEligibleBankAccountsForCard(bankAccountList);
+
+ /**
+ * Handle toggle change for Central Invoicing.
+ * When turning ON, triggers bank account flow if needed.
+ * When turning OFF, unassigns the settlement account.
+ */
+ const handleToggle = (isEnabled: boolean) => {
+ if (!isEnabled) {
+ // Turning OFF - unassign the settlement account by setting paymentBankAccountID to 0
+ setTravelInvoicingSettlementAccount(policyID, workspaceAccountID, 0, cardSettings?.paymentBankAccountID);
+ return;
+ }
+
+ // Check if user is on a public domain - Travel Invoicing requires a private domain
+ if (account?.isFromPublicDomain) {
+ Navigation.navigate(ROUTES.TRAVEL_PUBLIC_DOMAIN_ERROR.getRoute(Navigation.getActiveRoute()));
+ return;
+ }
+
+ // Turning ON - check if bank account setup is needed
+ if (!eligibleBankAccounts.length || isSetupUnfinished) {
+ // No bank accounts - start add bank account flow
+ Navigation.navigate(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID, REIMBURSEMENT_ACCOUNT_ROUTE_NAMES.NEW, ROUTES.WORKSPACE_TRAVEL.getRoute(policyID)));
+ return;
+ }
+
+ // Bank accounts exist - go to settlement account selection
+ Navigation.navigate(ROUTES.WORKSPACE_TRAVEL_SETTINGS_ACCOUNT.getRoute(policyID));
+ };
+
const getCentralInvoicingSubtitle = () => {
- if (!isCentralInvoicingEnabled) {
+ if (!hasSettlementAccount) {
return } />;
}
return ;
@@ -91,8 +123,8 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec
title: translate('workspace.moreFeatures.travel.travelInvoicing.centralInvoicingSection.title'),
subtitle: getCentralInvoicingSubtitle(),
switchAccessibilityLabel: translate('workspace.moreFeatures.travel.travelInvoicing.centralInvoicingSection.subtitle'),
- isActive: isCentralInvoicingEnabled,
- onToggle: (isEnabled: boolean) => setIsCentralInvoicingEnabled(isEnabled),
+ isActive: hasSettlementAccount,
+ onToggle: handleToggle,
// pendingAction: policy?.pendingFields?.autoReporting ?? policy?.pendingFields?.autoReportingFrequency,
// errors: getLatestErrorField(policy ?? {}, CONST.POLICY.COLLECTION_KEYS.AUTOREPORTING),
// onCloseError: () => clearPolicyErrorField(route.params.policyID, CONST.POLICY.COLLECTION_KEYS.AUTOREPORTING),
@@ -185,17 +217,12 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec
);
- // If Travel Invoicing is not enabled or no settlement account is configured
- // show the BookOrManageYourTrip component as fallback
- if (!isTravelInvoicingEnabled || !hasSettlementAccount) {
- return ;
- }
-
return (
<>
diff --git a/tests/ui/WorkspaceTravelInvoicingSectionTest.tsx b/tests/ui/WorkspaceTravelInvoicingSectionTest.tsx
index 4f3a73fb74d4c..4ff078401df04 100644
--- a/tests/ui/WorkspaceTravelInvoicingSectionTest.tsx
+++ b/tests/ui/WorkspaceTravelInvoicingSectionTest.tsx
@@ -81,7 +81,7 @@ describe('WorkspaceTravelInvoicingSection', () => {
});
describe('When Travel Invoicing is not configured', () => {
- it('should show BookOrManageYourTrip when card settings are not available', async () => {
+ it('should render sections when card settings are not available', async () => {
// Given no Travel Invoicing card settings exist
await act(async () => {
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, mockPolicy);
@@ -94,13 +94,15 @@ describe('WorkspaceTravelInvoicingSection', () => {
// Wait for component to render
await waitForBatchedUpdatesWithAct();
- // Then the fallback component should be visible (BookOrManageYourTrip)
- expect(screen.getByText('Book or manage your trip')).toBeTruthy();
+ // Then the Travel Booking section should be visible
+ expect(screen.getByText('Travel booking')).toBeTruthy();
+ // And the Central Invoicing section should be visible
+ expect(screen.getByText('Central invoicing')).toBeTruthy();
});
- it('should show BookOrManageYourTrip when paymentBankAccountID is not set', async () => {
+ it('should render sections when paymentBankAccountID is not set', async () => {
// Given Travel Invoicing card settings exist but without paymentBankAccountID
- const travelInvoicingKey = `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${WORKSPACE_ACCOUNT_ID}_TRAVEL_US` as OnyxKey;
+ const travelInvoicingKey = `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${WORKSPACE_ACCOUNT_ID}_${CONST.TRAVEL.PROGRAM_TRAVEL_US}` as OnyxKey;
await act(async () => {
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, mockPolicy);
@@ -116,13 +118,15 @@ describe('WorkspaceTravelInvoicingSection', () => {
await waitForBatchedUpdatesWithAct();
- // Then the fallback component should be visible
- expect(screen.getByText('Book or manage your trip')).toBeTruthy();
+ // Then the Travel Booking section should be visible
+ expect(screen.getByText('Travel booking')).toBeTruthy();
+ // And the Central Invoicing section should be visible
+ expect(screen.getByText('Central invoicing')).toBeTruthy();
});
});
describe('When Travel Invoicing is configured', () => {
- const travelInvoicingKey = `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${WORKSPACE_ACCOUNT_ID}_TRAVEL_US` as OnyxKey;
+ const travelInvoicingKey = `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${WORKSPACE_ACCOUNT_ID}_${CONST.TRAVEL.PROGRAM_TRAVEL_US}` as OnyxKey;
const bankAccountKey = ONYXKEYS.BANK_ACCOUNT_LIST;
it('should render the section title when card settings are properly configured', async () => {
diff --git a/tests/unit/TravelInvoicingUtilsTest.ts b/tests/unit/TravelInvoicingUtilsTest.ts
index 74486947467de..99ad12a64a13c 100644
--- a/tests/unit/TravelInvoicingUtilsTest.ts
+++ b/tests/unit/TravelInvoicingUtilsTest.ts
@@ -12,8 +12,8 @@ import type ExpensifyCardSettings from '@src/types/onyx/ExpensifyCardSettings';
describe('TravelInvoicingUtils', () => {
describe('PROGRAM_TRAVEL_US constant', () => {
- it('Should be defined as TRAVEL_US', () => {
- expect(CONST.TRAVEL.PROGRAM_TRAVEL_US).toBe('TRAVEL_US');
+ it('Should be defined as PROGRAM_TRAVEL_US', () => {
+ expect(CONST.TRAVEL.PROGRAM_TRAVEL_US).toBe('PROGRAM_TRAVEL_US');
});
});