Skip to content

Commit c5f17fa

Browse files
committed
Add Xero Travel Invoicing payable account selector
1 parent 4c52779 commit c5f17fa

13 files changed

Lines changed: 305 additions & 2 deletions

File tree

src/CONST/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2718,6 +2718,7 @@ const CONST = {
27182718
REPORT_FIELD: 'REPORT_FIELD',
27192719
},
27202720
ACCOUNTING_METHOD: 'accountingMethod',
2721+
TRAVEL_INVOICING_PAYABLE_ACCOUNT: 'travelInvoicingPayableAccountID',
27212722
},
27222723

27232724
SAGE_INTACCT_MAPPING_VALUE: {

src/ROUTES.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,6 +3501,14 @@ const ROUTES = {
35013501
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
35023502
getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/accounting/xero/export` as const, backTo, false),
35033503
},
3504+
POLICY_ACCOUNTING_XERO_TRAVEL_INVOICING_CONFIGURATION: {
3505+
route: 'workspaces/:policyID/accounting/xero/export/travel-invoicing',
3506+
getRoute: (policyID: string) => `workspaces/${policyID}/accounting/xero/export/travel-invoicing` as const,
3507+
},
3508+
POLICY_ACCOUNTING_XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT: {
3509+
route: 'workspaces/:policyID/accounting/xero/export/travel-invoicing/payable-account',
3510+
getRoute: (policyID: string) => `workspaces/${policyID}/accounting/xero/export/travel-invoicing/payable-account` as const,
3511+
},
35043512
POLICY_ACCOUNTING_XERO_PREFERRED_EXPORTER_SELECT: {
35053513
route: 'workspaces/:policyID/connections/xero/export/preferred-exporter/select',
35063514

src/SCREENS.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,8 @@ const SCREENS = {
540540
QUICKBOOKS_ONLINE_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT: 'Workspace_Accounting_Quickbooks_Online_Travel_Invoicing_Payable_Account_Select',
541541
NETSUITE_TRAVEL_INVOICING_CONFIGURATION: 'Workspace_Accounting_NetSuite_Travel_Invoicing_Configuration',
542542
NETSUITE_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT: 'Workspace_Accounting_NetSuite_Travel_Invoicing_Payable_Account_Select',
543+
XERO_TRAVEL_INVOICING_CONFIGURATION: 'Workspace_Accounting_Xero_Travel_Invoicing_Configuration',
544+
XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT: 'Workspace_Accounting_Xero_Travel_Invoicing_Payable_Account_Select',
543545
DYNAMIC_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES: 'Dynamic_Workspace_Accounting_Quickbooks_Online_Export_Out_Of_Pocket_Expenses',
544546
DYNAMIC_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_SELECT: 'Dynamic_Workspace_Accounting_Quickbooks_Online_Export_Out_Of_Pocket_Expenses_Select',
545547
DYNAMIC_QUICKBOOKS_ONLINE_EXPORT_OUT_OF_POCKET_EXPENSES_ACCOUNT_SELECT: 'Dynamic_Workspace_Accounting_Quickbooks_Online_Export_Out_Of_Pocket_Expenses_Account_Select',

src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,10 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
690690
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/xero/export/XeroExportConfigurationPage').default,
691691
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_PURCHASE_BILL_DATE_SELECT]: () =>
692692
require<ReactComponentModule>('../../../../pages/workspace/accounting/xero/export/XeroPurchaseBillDateSelectPage').default,
693+
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_CONFIGURATION]: () =>
694+
require<ReactComponentModule>('../../../../pages/workspace/accounting/xero/export/XeroTravelInvoicingConfigurationPage').default,
695+
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT]: () =>
696+
require<ReactComponentModule>('../../../../pages/workspace/accounting/xero/export/XeroTravelInvoicingPayableAccountSelectPage').default,
693697
[SCREENS.WORKSPACE.ACCOUNTING.DYNAMIC_XERO_EXPORT_BANK_ACCOUNT_SELECT]: () =>
694698
require<ReactComponentModule>('../../../../pages/workspace/accounting/xero/export/DynamicXeroBankAccountSelectPage').default,
695699
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/xero/advanced/XeroAdvancedPage').default,

src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const WORKSPACE_TO_RHP: Partial<Record<keyof WorkspaceSplitNavigatorParamList, s
5454
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT,
5555
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TRAVEL_INVOICING_CONFIGURATION,
5656
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT,
57+
SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_CONFIGURATION,
58+
SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT,
5759
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_NON_REIMBURSABLE_DEFAULT_VENDOR_SELECT,
5860
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_ADVANCED,
5961
SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_ACCOUNT_SELECTOR,

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,8 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
576576
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT]: {
577577
path: ROUTES.POLICY_ACCOUNTING_NETSUITE_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT.route,
578578
},
579+
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_CONFIGURATION]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRAVEL_INVOICING_CONFIGURATION.route},
580+
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT.route},
579581
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_ADVANCED]: {
580582
path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED.route,
581583
},

src/libs/Navigation/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,12 @@ type SettingsNavigatorParamList = {
770770
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT]: {
771771
policyID: string;
772772
};
773+
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_CONFIGURATION]: {
774+
policyID: string;
775+
};
776+
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT]: {
777+
policyID: string;
778+
};
773779
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_COMPANY_CARD_EXPENSE_ACCOUNT_SELECT]: {
774780
policyID: string;
775781
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md

src/libs/actions/connections/Xero.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
44
import Onyx from 'react-native-onyx';
55
import type {ValueOf} from 'type-fest';
66
import * as API from '@libs/API';
7-
import type {ConnectPolicyToAccountingIntegrationParams, UpdateXeroGenericTypeParams} from '@libs/API/parameters';
7+
import type {ConnectPolicyToAccountingIntegrationParams, UpdateManyPolicyConnectionConfigurationsParams, UpdateXeroGenericTypeParams} from '@libs/API/parameters';
88
import type UpdateXeroAccountingMethodParams from '@libs/API/parameters/UpdateXeroAccountingMethodParams';
99
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
1010
import {getCommandURL} from '@libs/ApiUtils';
@@ -497,6 +497,17 @@ function updateXeroExportNonReimbursableAccount(
497497
API.write(WRITE_COMMANDS.UPDATE_XERO_EXPORT_NON_REIMBURSABLE_ACCOUNT, parameters, {optimisticData, failureData, successData});
498498
}
499499

500+
function updateXeroTravelInvoicingPayableAccount(policyID: string, accountID: string, oldAccountID?: string) {
501+
const {optimisticData, failureData, successData} = prepareXeroOptimisticData(policyID, CONST.XERO_CONFIG.TRAVEL_INVOICING_PAYABLE_ACCOUNT, accountID, oldAccountID);
502+
const parameters: UpdateManyPolicyConnectionConfigurationsParams = {
503+
policyID,
504+
connectionName: CONST.POLICY.CONNECTIONS.NAME.XERO,
505+
configUpdate: JSON.stringify({[CONST.XERO_CONFIG.TRAVEL_INVOICING_PAYABLE_ACCOUNT]: accountID}),
506+
idempotencyKey: CONST.XERO_CONFIG.TRAVEL_INVOICING_PAYABLE_ACCOUNT,
507+
};
508+
API.write(WRITE_COMMANDS.UPDATE_MANY_POLICY_CONNECTION_CONFIGS, parameters, {optimisticData, failureData, successData});
509+
}
510+
500511
function updateXeroSyncInvoiceCollectionsAccountID(
501512
policyID: string,
502513
invoiceCollectionsAccountID: Partial<Connections['xero']['config']['sync']['invoiceCollectionsAccountID']>,
@@ -588,6 +599,7 @@ export {
588599
updateXeroExportExporter,
589600
updateXeroExportBillDate,
590601
updateXeroExportNonReimbursableAccount,
602+
updateXeroTravelInvoicingPayableAccount,
591603
updateXeroSyncInvoiceCollectionsAccountID,
592604
updateXeroSyncSyncReimbursedReports,
593605
updateXeroSyncReimbursementAccountID,

src/pages/workspace/accounting/xero/export/XeroExportConfigurationPage.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ import ConnectionLayout from '@components/ConnectionLayout';
44
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
55
import OfflineWithFeedback from '@components/OfflineWithFeedback';
66
import useLocalize from '@hooks/useLocalize';
7+
import useOnyx from '@hooks/useOnyx';
8+
import usePermissions from '@hooks/usePermissions';
79
import useThemeStyles from '@hooks/useThemeStyles';
10+
import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID';
11+
import {getCardSettings} from '@libs/CardUtils';
812
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
913
import Navigation from '@libs/Navigation/Navigation';
1014
import {areSettingsInErrorFields, getCurrentXeroOrganizationName, settingsPendingAction} from '@libs/PolicyUtils';
15+
import {getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey} from '@libs/TravelInvoicingUtils';
1116
import goBackFromExportConnection from '@navigation/helpers/goBackFromExportConnection';
1217
import type {PlatformStackRouteProp} from '@navigation/PlatformStackNavigation/types';
1318
import type {SettingsNavigatorParamList} from '@navigation/types';
@@ -20,19 +25,26 @@ import type SCREENS from '@src/SCREENS';
2025
function XeroExportConfigurationPage({policy}: WithPolicyConnectionsProps) {
2126
const {translate} = useLocalize();
2227
const styles = useThemeStyles();
28+
const {isBetaEnabled} = usePermissions();
2329
const policyID = policy?.id;
2430
const route = useRoute<PlatformStackRouteProp<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT>>();
2531
const backTo = route?.params?.backTo;
2632
const policyOwner = policy?.owner ?? '';
2733

28-
const {export: exportConfiguration, errorFields, pendingFields} = policy?.connections?.xero?.config ?? {};
34+
const {export: exportConfiguration, errorFields, pendingFields, travelInvoicingPayableAccountID} = policy?.connections?.xero?.config ?? {};
2935
const shouldGoBackToSpecificRoute = !exportConfiguration?.nonReimbursableAccount;
3036

3137
const goBack = useCallback(() => {
3238
return goBackFromExportConnection(shouldGoBackToSpecificRoute, backTo);
3339
}, [backTo, shouldGoBackToSpecificRoute]);
3440

3541
const {bankAccounts} = policy?.connections?.xero?.data ?? {};
42+
const travelPayableAccount = bankAccounts?.find((bank) => bank.id === travelInvoicingPayableAccountID);
43+
const workspaceAccountID = useWorkspaceAccountID(policyID);
44+
const [cardSettings] = useOnyx(getTravelInvoicingCardSettingsKey(workspaceAccountID));
45+
const travelSettings = getCardSettings(cardSettings, CONST.TRAVEL.PROGRAM_TRAVEL_US);
46+
const isTravelInvoicingEnabled = isBetaEnabled(CONST.BETAS.TRAVEL_INVOICING) && getIsTravelInvoicingEnabled(travelSettings);
47+
3648
const selectedBankAccountName = useMemo(() => {
3749
const selectedAccount = (bankAccounts ?? []).find((bank) => bank.id === exportConfiguration?.nonReimbursableAccount);
3850
return selectedAccount?.name ?? bankAccounts?.[0]?.name ?? '';
@@ -77,6 +89,16 @@ function XeroExportConfigurationPage({policy}: WithPolicyConnectionsProps) {
7789
shouldShowRightIcon: false,
7890
helperText: translate('workspace.xero.exportInvoicesDescription'),
7991
},
92+
...(isTravelInvoicingEnabled
93+
? [
94+
{
95+
title: travelPayableAccount?.name,
96+
description: translate('workspace.common.travelInvoicing'),
97+
onPress: !policyID ? undefined : () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_TRAVEL_INVOICING_CONFIGURATION.getRoute(policyID)),
98+
subscribedSettings: [CONST.XERO_CONFIG.TRAVEL_INVOICING_PAYABLE_ACCOUNT],
99+
},
100+
]
101+
: []),
80102
{
81103
description: translate('workspace.accounting.exportCompanyCard'),
82104
title: translate('workspace.xero.bankTransactions'),
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import React from 'react';
2+
import type {ValueOf} from 'type-fest';
3+
import ConnectionLayout from '@components/ConnectionLayout';
4+
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
5+
import OfflineWithFeedback from '@components/OfflineWithFeedback';
6+
import useLocalize from '@hooks/useLocalize';
7+
import useThemeStyles from '@hooks/useThemeStyles';
8+
import {areSettingsInErrorFields, settingsPendingAction} from '@libs/PolicyUtils';
9+
import Navigation from '@navigation/Navigation';
10+
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
11+
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
12+
import CONST from '@src/CONST';
13+
import ROUTES from '@src/ROUTES';
14+
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
15+
16+
type XeroSectionType = {
17+
title?: string;
18+
description?: string;
19+
onPress: () => void;
20+
subscribedSettings: string[];
21+
pendingAction?: PendingAction;
22+
brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>;
23+
};
24+
25+
const payableAccountSetting = [CONST.XERO_CONFIG.TRAVEL_INVOICING_PAYABLE_ACCOUNT];
26+
27+
function XeroTravelInvoicingConfigurationPage({policy}: WithPolicyConnectionsProps) {
28+
const {translate} = useLocalize();
29+
const styles = useThemeStyles();
30+
31+
const policyID = policy?.id ?? String(CONST.DEFAULT_NUMBER_ID);
32+
const config = policy?.connections?.xero?.config;
33+
34+
const {bankAccounts} = policy?.connections?.xero?.data ?? {};
35+
const travelPayableAccount = bankAccounts?.find((account) => account.id === config?.travelInvoicingPayableAccountID);
36+
37+
const sections: XeroSectionType[] = [
38+
{
39+
title: travelPayableAccount?.name,
40+
description: translate('workspace.common.travelInvoicingPayableAccount'),
41+
onPress: () => {
42+
if (!policyID) {
43+
return;
44+
}
45+
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_TRAVEL_INVOICING_PAYABLE_ACCOUNT_SELECT.getRoute(policyID));
46+
},
47+
subscribedSettings: payableAccountSetting,
48+
pendingAction: settingsPendingAction(payableAccountSetting, config?.pendingFields),
49+
brickRoadIndicator: areSettingsInErrorFields(payableAccountSetting, config?.errorFields) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
50+
},
51+
];
52+
53+
return (
54+
<ConnectionLayout
55+
displayName="XeroTravelInvoicingConfigurationPage"
56+
headerTitle="workspace.common.travelInvoicing"
57+
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN]}
58+
policyID={policyID}
59+
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
60+
contentContainerStyle={styles.pb2}
61+
titleStyle={styles.ph5}
62+
connectionName={CONST.POLICY.CONNECTIONS.NAME.XERO}
63+
onBackButtonPress={() => Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_EXPORT.getRoute(policyID))}
64+
>
65+
{sections.map((section) => (
66+
<OfflineWithFeedback
67+
pendingAction={section.pendingAction}
68+
key={section.subscribedSettings.at(0)}
69+
errorRowStyles={[styles.ph5]}
70+
>
71+
<MenuItemWithTopDescription
72+
title={section.title}
73+
description={section.description}
74+
onPress={section.onPress}
75+
shouldShowRightIcon
76+
brickRoadIndicator={section.brickRoadIndicator}
77+
/>
78+
</OfflineWithFeedback>
79+
))}
80+
</ConnectionLayout>
81+
);
82+
}
83+
84+
export default withPolicyConnections(XeroTravelInvoicingConfigurationPage);

0 commit comments

Comments
 (0)