Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Invoice collection account selector page #41511

Merged
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1272,6 +1272,7 @@ const CONST = {
SYNC: 'sync',
IMPORT_CUSTOMERS: 'importCustomers',
IMPORT_TAX_RATES: 'importTaxRates',
INVOICE_COLLECTION_ACCOUNT_ID: 'invoiceCollectionsAccountID',
},

QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE: {
Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/xero/advanced',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced` as const,
},
POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR: {
route: 'settings/workspaces/:policyID/accounting/xero/advanced/invoice-account-selector',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/advanced/invoice-account-selector` as const,
},
POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT: {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import` as const,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ const SCREENS = {
XERO_CUSTOMER: 'Policy_Acounting_Xero_Import_Customer',
XERO_TAXES: 'Policy_Accounting_Xero_Taxes',
XERO_ADVANCED: 'Policy_Accounting_Xero_Advanced',
XERO_INVOICE_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Invoice_Account_Selector',
},
INITIAL: 'Workspace_Initial',
PROFILE: 'Workspace_Profile',
Expand Down
81 changes: 81 additions & 0 deletions src/components/SelectionScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
import useLocalize from '@hooks/useLocalize';
import type {PolicyAccessVariant} from '@pages/workspace/AccessOrNotFoundWrapper';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {TranslationPaths} from '@src/languages/types';
import type {PolicyFeatureName} from '@src/types/onyx/Policy';
import HeaderWithBackButton from './HeaderWithBackButton';
import ScreenWrapper from './ScreenWrapper';
import SelectionList from './SelectionList';
import type RadioListItem from './SelectionList/RadioListItem';
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
import type TableListItem from './SelectionList/TableListItem';
import type {ListItem, SectionListDataType} from './SelectionList/types';
import type UserListItem from './SelectionList/UserListItem';

type SelectorType = ListItem & {
value: string;
};

type SelectionScreenProps = {
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
displayName: string;
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
title: TranslationPaths;
headerContent?: React.ReactNode;
sections: Array<SectionListDataType<SelectorType>>;
listItem: typeof RadioListItem | typeof UserListItem | typeof TableListItem;
initiallyFocusedOptionKey?: string | null | undefined;
onSelectRow: (selection: SelectorType) => void;
onBackButtonPress: () => void;
/** The current policyID */
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
policyID: string;
/** Defines which types of access should be verified */
accessVariants?: PolicyAccessVariant[];
/** The current feature name that the user tries to get access to */
featureName?: PolicyFeatureName;
};

function SelectionScreen({
displayName,
title,
headerContent,
sections,
listItem,
initiallyFocusedOptionKey,
onSelectRow,
onBackButtonPress,
policyID,
accessVariants,
featureName,
}: SelectionScreenProps) {
const {translate} = useLocalize();
return (
<AccessOrNotFoundWrapper
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
policyID={policyID}
accessVariants={accessVariants}
featureName={featureName}
>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={displayName}
>
<HeaderWithBackButton
title={translate(title)}
onBackButtonPress={onBackButtonPress}
/>
<SelectionList
onSelectRow={onSelectRow}
headerContent={headerContent}
sections={sections}
ListItem={listItem}
showScrollIndicator
shouldShowTooltips={false}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
);
}

export type {SelectorType};

SelectionScreen.displayName = 'SelectionScreen';
export default SelectionScreen;
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2010,6 +2010,7 @@ export default {
reimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the Xero account below.',
xeroBillPaymentAccount: 'Xero Bill Payment Account',
xeroInvoiceCollectionAccount: 'Xero Invoice Collections Account',
invoiceAccountSelectDescription: "As you've enabled exporting Invoices from Expensify to Xero, this is the account the Invoice will appear against once marked as Paid.",
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
},
},
type: {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2044,6 +2044,8 @@ export default {
'Cada vez que se pague un informe utilizando Expensify ACH, se creará el correspondiente pago de la factura en la cuenta de Xero indicadas a continuación.',
xeroBillPaymentAccount: 'Cuenta de pago de las facturas de Xero',
xeroInvoiceCollectionAccount: 'Cuenta de cobro de las facturas Xero',
invoiceAccountSelectDescription:
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
'Como ha activado la exportación de facturas de Expensify a Xero, esta es la cuenta en la que aparecerá la factura una vez marcada como Pagada.',
},
},
type: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER]: () => require('../../../../pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES]: () => require('../../../../pages/workspace/accounting/xero/XeroTaxesConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: () => require('../../../../pages/workspace/accounting/xero/advanced/XeroAdvancedPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: () =>
require('../../../../pages/workspace/accounting/xero/advanced/XeroInvoiceAccountSelectorPage').default as React.ComponentType,
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: () => require('../../../../pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: () =>
require('../../../../pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage').default as React.ComponentType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER,
SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES,
SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED,
SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR,
],
[SCREENS.WORKSPACE.TAXES]: [
SCREENS.WORKSPACE.TAXES_SETTINGS,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER]: {path: ROUTES.POLICY_ACCOUNTING_XERO_CUSTOMER.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TAXES.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: {path: ROUTES.POLICY_ACCOUNTING_XERO_ADVANCED.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR.route},
[SCREENS.WORKSPACE.DESCRIPTION]: {
path: ROUTES.WORKSPACE_PROFILE_DESCRIPTION.route,
},
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR]: {
policyID: string;
};
[SCREENS.GET_ASSISTANCE]: {
backTo: Routes;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import ConnectionLayout from '@components/ConnectionLayout';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
Expand All @@ -11,15 +11,24 @@ import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
import * as Policy from '@userActions/Policy';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import Navigation from '@libs/Navigation/Navigation';

function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const policyID = policy?.id ?? '';
const xeroConfig = policy?.connections?.xero?.config;
const {autoSync, pendingFields, sync} = xeroConfig ?? {};
const xeroData = policy?.connections?.xero?.data;
const xeroConfig = policy?.connections?.xero?.config;
const {autoSync, pendingFields, sync, } = xeroConfig ?? {};
const {bankAccounts} = policy?.connections?.xero?.data ?? {};

const selectedBankAccountName = useMemo(() => {
const {invoiceCollectionsAccountID} = sync ?? {};
const selectedAccount = (bankAccounts ?? []).find((bank) => bank.id === invoiceCollectionsAccountID);
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved

return selectedAccount?.name ?? '';
}, [sync, bankAccounts]);

return (
<ConnectionLayout
Expand Down Expand Up @@ -76,7 +85,7 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
<OfflineWithFeedback pendingAction={pendingFields?.sync}>
<MenuItemWithTopDescription
shouldShowRightIcon
title={String(xeroData?.bankAccounts)}
title={String(bankAccounts)}
description={translate('workspace.xero.advancedConfig.xeroBillPaymentAccount')}
key={translate('workspace.xero.advancedConfig.xeroBillPaymentAccount')}
wrapperStyle={[styles.sectionMenuItemTopDescription]}
Expand All @@ -86,11 +95,13 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) {
<OfflineWithFeedback pendingAction={pendingFields?.sync}>
<MenuItemWithTopDescription
shouldShowRightIcon
title={String(xeroData?.bankAccounts)}
title={String(selectedBankAccountName)}
description={translate('workspace.xero.advancedConfig.xeroInvoiceCollectionAccount')}
key={translate('workspace.xero.advancedConfig.xeroInvoiceCollectionAccount')}
wrapperStyle={[styles.sectionMenuItemTopDescription]}
onPress={() => {}}
onPress={() => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_INVOICE_SELECTOR.getRoute(policyID));
}}
/>
</OfflineWithFeedback>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import RadioListItem from '@components/SelectionList/RadioListItem';
import type {SelectorType} from '@components/SelectionScreen';
import SelectionScreen from '@components/SelectionScreen';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Connections from '@libs/actions/connections';
import Navigation from '@libs/Navigation/Navigation';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

function XeroInvoiceAccountSelectorPage({policy}: WithPolicyConnectionsProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const policyID = policy?.id ?? '';
const {bankAccounts} = policy?.connections?.xero?.data ?? {};

const {invoiceCollectionsAccountID} = policy?.connections?.xero?.config.sync ?? {};

const xeroSelectorOptions = useMemo<SelectorType[]>(
() =>
(bankAccounts ?? []).map(({id, name}) => ({
value: id,
text: name,
keyForList: id,
isSelected: invoiceCollectionsAccountID === id,
})),
[invoiceCollectionsAccountID, bankAccounts],
);

const listHeaderComponent = useMemo(
() => (
<View style={[styles.pb2, styles.ph5]}>
<Text style={[styles.pb5, styles.textNormal]}>{translate('workspace.xero.advancedConfig.invoiceAccountSelectDescription')}</Text>
</View>
),
[translate, styles.pb2, styles.ph5, styles.pb5, styles.textNormal],
);

const initiallyFocusedOptionKey = useMemo(() => xeroSelectorOptions?.find((mode) => mode.isSelected)?.keyForList, [xeroSelectorOptions]);

const updateMode = useCallback(
({value}: SelectorType) => {
Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.SYNC, {
invoiceCollectionsAccountID: value,
});
Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_ADVANCED.getRoute(policyID));
},
[policyID],
);

return (
<SelectionScreen
policyID={policyID}
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
displayName={XeroInvoiceAccountSelectorPage.displayName}
sections={[{data: xeroSelectorOptions}]}
listItem={RadioListItem}
onSelectRow={updateMode}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
headerContent={listHeaderComponent}
onBackButtonPress={() => Navigation.goBack()}
hungvu193 marked this conversation as resolved.
Show resolved Hide resolved
title="workspace.xero.advancedConfig.xeroInvoiceCollectionAccount"
/>
);
}

XeroInvoiceAccountSelectorPage.displayName = 'XeroInvoiceAccountSelectorPage';

export default withPolicyConnections(XeroInvoiceAccountSelectorPage);
2 changes: 1 addition & 1 deletion src/types/onyx/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ type Tenant = {
};

type XeroConnectionData = {
bankAccounts: unknown[];
bankAccounts: Account[];
countryCode: string;
organisationID: string;
revenueAccounts: Array<{
Expand Down
Loading