diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 6e69d2d4e53f..bbcc5d937ba3 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -761,6 +761,14 @@ const ROUTES = { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const, }, + POLICY_ACCOUNTING_XERO_IMPORT: { + route: 'settings/workspaces/:policyID/accounting/xero/import', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import` as const, + }, + POLICY_ACCOUNTING_XERO_ORGANIZATION: { + route: 'settings/workspaces/:policyID/accounting/xero/organization/:currentOrganizationID', + getRoute: (policyID: string, currentOrganizationID: string) => `settings/workspaces/${policyID}/accounting/xero/organization/${currentOrganizationID}` 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, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 725cab85f12b..cf97c0ea2c0d 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -233,6 +233,8 @@ const SCREENS = { QUICKBOOKS_ONLINE_ADVANCED: 'Policy_Accounting_Quickbooks_Online_Advanced', QUICKBOOKS_ONLINE_ACCOUNT_SELECTOR: 'Policy_Accounting_Quickbooks_Online_Account_Selector', QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECTOR: 'Policy_Accounting_Quickbooks_Online_Invoice_Account_Selector', + XERO_IMPORT: 'Policy_Accounting_Xero_Import', + XERO_ORGANIZATION: 'Policy_Accounting_Xero_Customers', }, INITIAL: 'Workspace_Initial', PROFILE: 'Workspace_Profile', diff --git a/src/languages/en.ts b/src/languages/en.ts index 39b648d110c4..656d6cbd31c3 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1882,17 +1882,12 @@ export default { `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`, }, qbo: { - import: 'Import', importDescription: 'Choose which coding configurations are imported from QuickBooks Online to Expensify.', classes: 'Classes', - accounts: 'Chart of accounts', locations: 'Locations', - taxes: 'Taxes', customers: 'Customers/Projects', - imported: 'Imported', displayedAs: 'Displayed as', notImported: 'Not imported', - importedAsTags: 'Imported, displayed as tags', importedAsReportFields: 'Imported, displayed as report fields', accountsDescription: 'Chart of Accounts import as categories when connected to an accounting integration, this cannot be disabled.', accountsSwitchTitle: 'Enable newly imported Chart of Accounts.', @@ -1992,6 +1987,13 @@ export default { 'If you are exporting invoices from Expensify to Quickbooks Online, this is the account the invoice will appear against once marked as paid.', }, }, + xero: { + organization: 'Xero organization', + organizationDescription: 'Select the organization in Xero you are importing data from.', + importDescription: 'Choose which coding configurations are imported from Xero to Expensify.', + trackingCategories: 'Tracking categories', + customers: 'Re-bill customers', + }, type: { free: 'Free', control: 'Control', @@ -2220,6 +2222,10 @@ export default { } } }, + accounts: 'Chart of accounts', + taxes: 'Taxes', + imported: 'Imported', + importedAsTags: 'Imported, displayed as tags', disconnectPrompt: (integrationToConnect?: ConnectionName, currentIntegration?: ConnectionName): string => { switch (integrationToConnect) { case CONST.POLICY.CONNECTIONS.NAME.QBO: diff --git a/src/languages/es.ts b/src/languages/es.ts index c762d76fe070..aaab540d1659 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1906,17 +1906,12 @@ export default { `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`, }, qbo: { - import: 'Importación', importDescription: 'Elige que configuraciónes de codificación son importadas desde QuickBooks Online a Expensify.', classes: 'Clases', - accounts: 'Plan de cuentas', locations: 'Lugares', - taxes: 'Impuestos', customers: 'Clientes/Proyectos', - imported: 'Importado', displayedAs: 'Mostrado como', notImported: 'No importado', - importedAsTags: 'Importado, mostrado como etiqueta', importedAsReportFields: 'Importado, mostrado como campo de informe', accountsDescription: 'Los planes de cuentas se importan como categorías cuando está conectado con una integración de contaduría, esto no se puede desactivar.', accountsSwitchTitle: 'Habilita el plan de cuentas recien importado', @@ -2019,6 +2014,13 @@ export default { 'Si está exportando facturas de Expensify a Quickbooks Online, ésta es la cuenta en la que aparecerá la factura una vez marcada como pagada.', }, }, + xero: { + organization: 'Organización Xero', + organizationDescription: 'Seleccione la organización en Xero desde la que está importando los datos.', + importDescription: 'Elija qué configuraciones de codificación se importan de Xero a Expensify.', + trackingCategories: 'Categorías de seguimiento', + customers: 'Volver a facturar a los clientes', + }, type: { free: 'Gratis', control: 'Control', @@ -2215,6 +2217,10 @@ export default { } } }, + accounts: 'Plan de cuentas', + taxes: 'Impuestos', + imported: 'Importado', + importedAsTags: 'Importado, mostrado como etiqueta', disconnectPrompt: (integrationToConnect?: ConnectionName, currentIntegration?: ConnectionName): string => { switch (integrationToConnect) { case CONST.POLICY.CONNECTIONS.NAME.QBO: diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 96ae6fdfffcc..2ae41e493a8f 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -291,6 +291,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/qbo/advanced/QuickbooksInvoiceAccountSelectPage').default as React.ComponentType, + + [SCREENS.WORKSPACE.ACCOUNTING.XERO_IMPORT]: () => require('../../../../pages/workspace/accounting/xero/XeroImportPage').default as React.ComponentType, + [SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION]: () => require('../../../../pages/workspace/accounting/xero/XeroOrganizationConfigurationPage').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, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index b2aa7ad35a05..a4c3a399f869 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -39,6 +39,8 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_ADVANCED, SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_ACCOUNT_SELECTOR, SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECTOR, + SCREENS.WORKSPACE.ACCOUNTING.XERO_IMPORT, + SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION, ], [SCREENS.WORKSPACE.TAXES]: [ SCREENS.WORKSPACE.TAXES_SETTINGS, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index aa0e7fd20924..1e2075b53acc 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -320,6 +320,8 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECTOR]: { path: ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_INVOICE_ACCOUNT_SELECTOR.route, }, + [SCREENS.WORKSPACE.ACCOUNTING.XERO_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_IMPORT.route}, + [SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION]: {path: ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.route}, [SCREENS.WORKSPACE.DESCRIPTION]: { path: ROUTES.WORKSPACE_PROFILE_DESCRIPTION.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index cf999631a803..e8a1c2c69c33 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -307,6 +307,13 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_EXPORT_PREFERRED_EXPORTER]: { policyID: string; }; + [SCREENS.WORKSPACE.ACCOUNTING.XERO_IMPORT]: { + policyID: string; + }; + [SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION]: { + policyID: string; + organizationID: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 3ef4281a0912..77b8c7b97f56 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -18,6 +18,7 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type SCREENS from '@src/SCREENS'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import AccessOrNotFoundWrapper from './AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; @@ -47,8 +48,8 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); const {canUseAccountingIntegrations} = usePermissions(); - const hasAccountingConnection = !!policy?.areConnectionsEnabled && !!policy?.connections; - const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config?.syncTax; + const hasAccountingConnection = !!policy?.areConnectionsEnabled && !isEmptyObject(policy?.connections); + const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config.syncTax || !!policy?.connections?.xero?.config.importTaxRates; const spendItems: Item[] = [ { diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 4fd8ea019370..48888c054813 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -33,7 +33,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx'; -import type {PolicyConnectionName} from '@src/types/onyx/Policy'; +import type {PolicyConnectionName, Tenant} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -91,7 +91,7 @@ function accountingIntegrationData( integrationToDisconnect={integrationToDisconnect} /> ), - onImportPagePress: () => {}, + onImportPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_IMPORT.getRoute(policyID)), onExportPagePress: () => {}, onAdvancedPagePress: () => {}, }; @@ -114,6 +114,18 @@ function PolicyAccountingPage({policy, connectionSyncProgress}: PolicyAccounting const connectedIntegration = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]) ?? connectionSyncProgress?.connectionName; const policyID = policy?.id ?? ''; + const policyConnectedToXero = connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.XERO; + + const tenants = useMemo(() => { + // Due to the way optional chain is being handled in this useMemo we are forced to use this approach to properly handle undefined values + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain + if (!policy || !policy.connections || !policy.connections.xero || !policy.connections.xero.data) { + return []; + } + return policy?.connections?.xero?.data?.tenants ?? []; + }, [policy]); + const currentXeroOrganization = tenants.find((tenant) => tenant.id === policy?.connections?.xero.config.tenantID); + const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( () => [ { @@ -185,6 +197,24 @@ function PolicyAccountingPage({policy, connectionSyncProgress}: PolicyAccounting ), }, + ...(policyConnectedToXero + ? [ + { + description: translate('workspace.xero.organization'), + iconRight: Expensicons.ArrowRight, + title: currentXeroOrganization?.name, + wrapperStyle: [styles.sectionMenuItemTopDescription], + shouldShowRightIcon: tenants.length > 1, + shouldShowDescriptionOnTop: true, + onPress: () => { + if (!(tenants.length > 1)) { + return; + } + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_ORGANIZATION.getRoute(policyID, currentXeroOrganization?.id ?? '')); + }, + }, + ] + : []), ...(isEmptyObject(policy?.connections) ? [] : [ @@ -217,9 +247,12 @@ function PolicyAccountingPage({policy, connectionSyncProgress}: PolicyAccounting }, [ connectedIntegration, connectionSyncProgress?.stageInProgress, + currentXeroOrganization, + tenants, isSyncInProgress, overflowMenu, policy?.connections, + policyConnectedToXero, policyID, styles, theme.spinner, diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx index 96cb0115d29a..9322455f1d4c 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx @@ -33,7 +33,7 @@ function QuickbooksChartOfAccountsPage({policy}: WithPolicyProps) { shouldEnableMaxHeight testID={QuickbooksChartOfAccountsPage.displayName} > - + {translate('workspace.qbo.accountsDescription')} @@ -43,7 +43,7 @@ function QuickbooksChartOfAccountsPage({policy}: WithPolicyProps) { Connections.updatePolicyConnectionConfig( diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksClassesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksClassesPage.tsx index d0cd62d043e9..45b7533648bb 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksClassesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksClassesPage.tsx @@ -40,7 +40,7 @@ function QuickbooksClassesPage({policy}: WithPolicyProps) { {translate('workspace.qbo.classesDescription')} - {translate('workspace.qbo.import')} + {translate('workspace.accounting.import')} diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksCustomersPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksCustomersPage.tsx index e0543a45e85d..3c38e542d455 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksCustomersPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksCustomersPage.tsx @@ -39,7 +39,7 @@ function QuickbooksCustomersPage({policy}: WithPolicyProps) { {translate('workspace.qbo.customersDescription')} - {translate('workspace.qbo.import')} + {translate('workspace.accounting.import')} diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx index 7d6ee38a1a29..e3e3eaf84591 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx @@ -18,11 +18,11 @@ function QuickbooksImportPage({policy}: WithPolicyProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const quickbooksOnlineConfigTitles = { - [CONST.INTEGRATION_ENTITY_MAP_TYPES.DEFAULT]: translate('workspace.qbo.imported'), - [CONST.INTEGRATION_ENTITY_MAP_TYPES.IMPORTED]: translate('workspace.qbo.imported'), + [CONST.INTEGRATION_ENTITY_MAP_TYPES.DEFAULT]: translate('workspace.accounting.imported'), + [CONST.INTEGRATION_ENTITY_MAP_TYPES.IMPORTED]: translate('workspace.accounting.imported'), [CONST.INTEGRATION_ENTITY_MAP_TYPES.NOT_IMPORTED]: translate('workspace.qbo.notImported'), [CONST.INTEGRATION_ENTITY_MAP_TYPES.NONE]: translate('workspace.qbo.notImported'), - [CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG]: translate('workspace.qbo.importedAsTags'), + [CONST.INTEGRATION_ENTITY_MAP_TYPES.TAG]: translate('workspace.accounting.importedAsTags'), [CONST.INTEGRATION_ENTITY_MAP_TYPES.REPORT_FIELD]: translate('workspace.qbo.importedAsReportFields'), }; const policyID = policy?.id ?? ''; @@ -30,7 +30,7 @@ function QuickbooksImportPage({policy}: WithPolicyProps) { const sections = [ { - description: translate('workspace.qbo.accounts'), + description: translate('workspace.accounting.accounts'), action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS.getRoute(policyID)), hasError: Boolean(policy?.errors?.enableNewCategories), title: enableNewCategories, @@ -61,7 +61,7 @@ function QuickbooksImportPage({policy}: WithPolicyProps) { if (policy?.connections?.quickbooksOnline.data.country !== CONST.COUNTRY.US) { sections.push({ - description: translate('workspace.qbo.taxes'), + description: translate('workspace.accounting.taxes'), action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_TAXES.getRoute(policyID)), hasError: Boolean(policy?.errors?.syncTaxes), title: syncTaxes, @@ -80,7 +80,7 @@ function QuickbooksImportPage({policy}: WithPolicyProps) { shouldEnableMaxHeight testID={QuickbooksImportPage.displayName} > - + {translate('workspace.qbo.importDescription')} {sections.map((section) => ( diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksLocationsPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksLocationsPage.tsx index b74ddbf28e3c..307847809d62 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksLocationsPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksLocationsPage.tsx @@ -40,7 +40,7 @@ function QuickbooksLocationsPage({policy}: WithPolicyProps) { {translate('workspace.qbo.locationsDescription')} - {translate('workspace.qbo.import')} + {translate('workspace.accounting.import')} diff --git a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx index 5112ad127681..e8a32a62d8b5 100644 --- a/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx +++ b/src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx @@ -32,17 +32,17 @@ function QuickbooksTaxesPage({policy}: WithPolicyProps) { shouldEnableMaxHeight testID={QuickbooksTaxesPage.displayName} > - + {translate('workspace.qbo.taxesDescription')} - {translate('workspace.qbo.import')} + {translate('workspace.accounting.import')} Connections.updatePolicyConnectionConfig( diff --git a/src/pages/workspace/accounting/xero/XeroImportPage.tsx b/src/pages/workspace/accounting/xero/XeroImportPage.tsx new file mode 100644 index 000000000000..3c1455e9ee65 --- /dev/null +++ b/src/pages/workspace/accounting/xero/XeroImportPage.tsx @@ -0,0 +1,112 @@ +import React, {useMemo} from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import CONST from '@src/CONST'; +import type {Tenant} from '@src/types/onyx/Policy'; + +function XeroImportPage({policy}: WithPolicyProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const policyID = policy?.id ?? ''; + const {importCustomers, importTaxRates, importTrackingCategories, pendingFields} = policy?.connections?.xero?.config ?? {}; + + const tenants = useMemo(() => policy?.connections?.xero?.data?.tenants ?? [], [policy?.connections?.xero.data.tenants]); + const currentXeroOrganization = tenants.find((tenant) => tenant.id === policy?.connections?.xero.config.tenantID); + + const sections = useMemo( + () => [ + { + description: translate('workspace.accounting.accounts'), + action: () => {}, + hasError: !!policy?.errors?.enableNewCategories, + title: translate('workspace.accounting.imported'), + pendingAction: pendingFields?.enableNewCategories, + }, + { + description: translate('workspace.xero.trackingCategories'), + action: () => {}, + hasError: !!policy?.errors?.importTrackingCategories, + title: importTrackingCategories ? translate('workspace.accounting.importedAsTags') : '', + pendingAction: pendingFields?.importTrackingCategories, + }, + { + description: translate('workspace.xero.customers'), + action: () => {}, + hasError: !!policy?.errors?.importCustomers, + title: importCustomers ? translate('workspace.accounting.importedAsTags') : '', + pendingAction: pendingFields?.importCustomers, + }, + { + description: translate('workspace.accounting.taxes'), + action: () => {}, + hasError: !!policy?.errors?.importTaxes, + title: importTaxRates ? translate('workspace.accounting.imported') : '', + pendingAction: pendingFields?.importTaxRates, + }, + ], + [ + importCustomers, + importTaxRates, + importTrackingCategories, + pendingFields?.enableNewCategories, + pendingFields?.importTaxRates, + pendingFields?.importCustomers, + pendingFields?.importTrackingCategories, + policy?.errors?.importTrackingCategories, + policy?.errors?.enableNewCategories, + policy?.errors?.importCustomers, + policy?.errors?.importTaxes, + translate, + ], + ); + + return ( + + + + + {translate('workspace.xero.importDescription')} + {sections.map((section) => ( + + + + ))} + + + + ); +} + +XeroImportPage.displayName = 'PolicyXeroImportPage'; + +export default withPolicy(XeroImportPage); diff --git a/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx new file mode 100644 index 000000000000..3411f524b7d4 --- /dev/null +++ b/src/pages/workspace/accounting/xero/XeroOrganizationConfigurationPage.tsx @@ -0,0 +1,75 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import type {ListItem} from '@components/SelectionList/types'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {updatePolicyConnectionConfig} from '@libs/actions/connections'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import withPolicy from '@pages/workspace/withPolicy'; +import type {WithPolicyProps} from '@pages/workspace/withPolicy'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; + +type XeroOrganizationConfigurationPageProps = WithPolicyProps & StackScreenProps; +function XeroOrganizationConfigurationPage({ + policy, + route: { + params: {organizationID}, + }, +}: XeroOrganizationConfigurationPageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const policyID = policy?.id ?? ''; + + const sections = + policy?.connections?.xero.data.tenants.map((tenant) => ({ + text: tenant.name, + keyForList: tenant.id, + isSelected: tenant.id === organizationID, + })) ?? []; + + const saveSelection = ({keyForList}: ListItem) => { + if (!keyForList) { + return; + } + + updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, 'tenantID', keyForList); + }; + + return ( + + + + + {translate('workspace.xero.organizationDescription')} + + + + + ); +} + +XeroOrganizationConfigurationPage.displayName = 'PolicyXeroOrganizationConfigurationPage'; + +export default withPolicy(XeroOrganizationConfigurationPage); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 106867660eeb..54d6198f4078 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -186,6 +186,71 @@ type QBOConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ exportCompanyCard: ValueOf; errorFields?: OnyxCommon.ErrorFields; }>; + +type BillStatusValues = 'DRAFT' | 'AWT_APPROVAL' | 'AWT_PAYMENT'; + +type ExpenseTypesValues = 'BILL' | 'BANK_TRANSACTION' | 'SALES_INVOICE' | 'NOTHING'; + +type BillDateValues = 'REPORT_SUBMITTED' | 'REPORT_EXPORTED' | 'LAST_EXPENSE'; + +type Tenant = { + id: string; + name: string; + value: string; +}; + +type XeroConnectionData = { + bankAccounts: unknown[]; + countryCode: string; + organisationID: string; + revenueAccounts: Array<{ + id: string; + name: string; + }>; + tenants: Tenant[]; + trackingCategories: unknown[]; +}; + +/** + * User configuration for the Xero accounting integration. + */ +type XeroConnectionConfig = OnyxCommon.OnyxValueWithOfflineFeedback<{ + autoSync: { + enabled: boolean; + jobID: string; + }; + enableNewCategories: boolean; + export: { + billDate: BillDateValues; + billStatus: { + purchase: BillStatusValues; + sales: BillStatusValues; + }; + billable: ExpenseTypesValues; + exporter: string; + nonReimbursable: ExpenseTypesValues; + nonReimbursableAccount: string; + reimbursable: ExpenseTypesValues; + }; + importCustomers: boolean; + importTaxRates: boolean; + importTrackingCategories: boolean; + isConfigured: boolean; + mappings: { + customer: string; + }; + sync: { + hasChosenAutoSyncOption: boolean; + hasChosenSyncReimbursedReportsOption: boolean; + invoiceCollectionsAccountID: string; + reimbursementAccountID: string; + syncReimbursedReports: boolean; + }; + tenantID: string; + errors?: OnyxCommon.Errors; + errorFields?: OnyxCommon.ErrorFields; +}>; + type Connection = { lastSync?: ConnectionLastSync; data: ConnectionData; @@ -194,7 +259,7 @@ type Connection = { type Connections = { quickbooksOnline: Connection; - xero: Connection; + xero: Connection; }; type ConnectionName = keyof Connections; @@ -479,5 +544,6 @@ export type { PolicyConnectionSyncProgress, Connections, ConnectionName, + Tenant, Account, };