Skip to content

Commit

Permalink
Merge pull request Expensify#41441 from mananjadhav/xero-import-categ…
Browse files Browse the repository at this point in the history
…ories

[Wave Collect][Xero] Import tracking categories
  • Loading branch information
lakchote authored May 8, 2024
2 parents 55c56bf + 3dc105d commit e0b6a06
Show file tree
Hide file tree
Showing 17 changed files with 350 additions and 8 deletions.
11 changes: 11 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,17 @@ const CONST = {
SYNC: 'sync',
IMPORT_CUSTOMERS: 'importCustomers',
IMPORT_TAX_RATES: 'importTaxRates',
IMPORT_TRACKING_CATEGORIES: 'importTrackingCategories',
MAPPINGS: 'mappings',
TRACKING_CATEGORY_PREFIX: 'trackingCategory_',
TRACKING_CATEGORY_FIELDS: {
COST_CENTERS: 'cost centers',
REGION: 'region',
},
TRACKING_CATEGORY_OPTIONS: {
DEFAULT: 'DEFAULT',
TAG: 'TAG',
},
},

QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE: {
Expand Down
12 changes: 12 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,18 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/xero/organization/:currentOrganizationID',
getRoute: (policyID: string, currentOrganizationID: string) => `settings/workspaces/${policyID}/accounting/xero/organization/${currentOrganizationID}` as const,
},
POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES: {
route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import/tracking-categories` as const,
},
POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_COST_CENTERS: {
route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories/cost-centers',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import/tracking-categories/cost-centers` as const,
},
POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_REGION: {
route: 'settings/workspaces/:policyID/accounting/xero/import/tracking-categories/region',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/xero/import/tracking-categories/region` as const,
},
POLICY_ACCOUNTING_XERO_CUSTOMER: {
route: '/settings/workspaces/:policyID/accounting/xero/import/customers',
getRoute: (policyID: string) => `/settings/workspaces/${policyID}/accounting/xero/import/customers` as const,
Expand Down
3 changes: 3 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ const SCREENS = {
XERO_ORGANIZATION: 'Policy_Accounting_Xero_Customers',
XERO_CUSTOMER: 'Policy_Acounting_Xero_Import_Customer',
XERO_TAXES: 'Policy_Accounting_Xero_Taxes',
XERO_TRACKING_CATEGORIES: 'Policy_Accounting_Xero_Tracking_Categories',
XERO_MAP_COST_CENTERS: 'Policy_Accounting_Xero_Map_Cost_Centers',
XERO_MAP_REGION: 'Policy_Accounting_Xero_Map_Region',
XERO_EXPORT: 'Policy_Accounting_Xero_Export',
XERO_ADVANCED: 'Policy_Accounting_Xero_Advanced',
XERO_INVOICE_ACCOUNT_SELECTOR: 'Policy_Accounting_Xero_Invoice_Account_Selector',
Expand Down
2 changes: 1 addition & 1 deletion src/components/ConnectToXeroButton/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Modal from '@components/Modal';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import getXeroSetupLink from '@libs/actions/connections/ConnectToXero';
import {getXeroSetupLink} from '@libs/actions/connections/ConnectToXero';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Session} from '@src/types/onyx';
Expand Down
2 changes: 1 addition & 1 deletion src/components/ConnectToXeroButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import getXeroSetupLink from '@libs/actions/connections/ConnectToXero';
import {getXeroSetupLink} from '@libs/actions/connections/ConnectToXero';
import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import type {ConnectToXeroButtonProps} from './types';
Expand Down
9 changes: 9 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2022,10 +2022,19 @@ export default {
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',
trackingCategoriesDescription: 'Choose whether to import tracking categories and see where they are displayed.',
mapXeroCostCentersTo: 'Map Xero cost centers to',
mapXeroRegionsTo: 'Map Xero regions to',
mapXeroCostCentersToDescription: 'Choose where to map cost centers to when exporting to Xero.',
mapXeroRegionsToDescription: 'Choose where to map employee regions when exporting expense reports to Xero.',
customers: 'Re-bill customers',
customersDescription: 'Import customer contacts. Billable expenses need tags for export. Expenses will carry the customer information to Xero for sales invoices.',
taxesDescription: 'Choose whether to import tax rates and tax defaults from your accounting integration.',
notImported: 'Not imported',
trackingCategoriesOptions: {
default: 'Xero contact default',
tag: 'Tags',
},
export: 'Export',
exportDescription: 'Configure how data in Expensify gets exported to Xero.',
exportCompanyCard: 'Export company card expenses as',
Expand Down
9 changes: 9 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2054,11 +2054,20 @@ export default {
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',
trackingCategoriesDescription: 'Elige si deseas importar categorías de seguimiento y ver dónde se muestran.',
mapXeroCostCentersTo: 'Asignar centros de coste de Xero a',
mapXeroRegionsTo: 'Asignar regiones de Xero a',
mapXeroCostCentersToDescription: 'Elige dónde mapear los centros de coste al exportar a Xero.',
mapXeroRegionsToDescription: 'Elige dónde asignar las regiones de los empleados al exportar informes de gastos a Xero.',
customers: 'Volver a facturar a los clientes',
customersDescription:
'Importar contactos de clientes. Los gastos facturables necesitan etiquetas para la exportación. Los gastos llevarán la información del cliente a Xero para las facturas de ventas.',
taxesDescription: 'Elige si quires importar las tasas de impuestos y los impuestos por defecto de tu integración de contaduría.',
notImported: 'No importado',
trackingCategoriesOptions: {
default: 'Contacto de Xero por defecto',
tag: 'Etiquetas',
},
export: 'Exportar',
exportDescription: 'Configura cómo se exportan los datos de Expensify a Xero.',
exportCompanyCard: 'Exportar gastos de la tarjeta de empresa como',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION]: () => require('../../../../pages/workspace/accounting/xero/XeroOrganizationConfigurationPage').default as React.ComponentType,
[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_TRACKING_CATEGORIES]: () =>
require('../../../../pages/workspace/accounting/xero/XeroTrackingCategoryConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS]: () => require('../../../../pages/workspace/accounting/xero/XeroMapCostCentersToConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION]: () => require('../../../../pages/workspace/accounting/xero/XeroMapRegionsToConfigurationPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT]: () => require('../../../../pages/workspace/accounting/xero/export/XeroExportConfigurationPage').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]: () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.ACCOUNTING.XERO_ORGANIZATION,
SCREENS.WORKSPACE.ACCOUNTING.XERO_CUSTOMER,
SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES,
SCREENS.WORKSPACE.ACCOUNTING.XERO_TRACKING_CATEGORIES,
SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS,
SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION,
SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT,
SCREENS.WORKSPACE.ACCOUNTING.XERO_ADVANCED,
SCREENS.WORKSPACE.ACCOUNTING.XERO_INVOICE_ACCOUNT_SELECTOR,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
},
[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.ACCOUNTING.XERO_TRACKING_CATEGORIES]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_COST_CENTERS.route},
[SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION]: {path: ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES_MAP_REGION.route},
[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_EXPORT]: {path: ROUTES.POLICY_ACCOUNTING_XERO_EXPORT.route},
Expand Down
9 changes: 9 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,15 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TAXES]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_TRACKING_CATEGORIES]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_COST_CENTERS]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_MAP_REGION]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT]: {
policyID: string;
};
Expand Down
28 changes: 27 additions & 1 deletion src/libs/actions/connections/ConnectToXero.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
import type {OnyxEntry} from 'react-native-onyx';
import type {ConnectPolicyToAccountingIntegrationParams} from '@libs/API/parameters';
import {READ_COMMANDS} from '@libs/API/types';
import {getCommandURL} from '@libs/ApiUtils';
import CONST from '@src/CONST';
import type * as OnyxTypes from '@src/types/onyx';
import type {XeroTrackingCategory} from '@src/types/onyx/Policy';

const getXeroSetupLink = (policyID: string) => {
const params: ConnectPolicyToAccountingIntegrationParams = {policyID};
const commandURL = getCommandURL({command: READ_COMMANDS.CONNECT_POLICY_TO_XERO, shouldSkipWebProxy: true});
return commandURL + new URLSearchParams(params).toString();
};

export default getXeroSetupLink;
/**
* Fetches the category object from the xero.data.trackingCategories based on the category name.
* This is required to get Xero category object with current value stored in the xero.config.mappings
* @param policy
* @param key
* @returns Filtered category matching the category name or undefined.
*/
const getTrackingCategory = (policy: OnyxEntry<OnyxTypes.Policy>, categoryName: string): (XeroTrackingCategory & {value: string}) | undefined => {
const {trackingCategories} = policy?.connections?.xero?.data ?? {};
const {mappings} = policy?.connections?.xero?.config ?? {};

const category = trackingCategories?.find((currentCategory) => currentCategory.name.toLowerCase() === categoryName.toLowerCase());
if (!category) {
return undefined;
}

return {
...category,
value: mappings?.[`${CONST.XERO_CONFIG.TRACKING_CATEGORY_PREFIX}${category.id}`] ?? '',
};
};

export {getXeroSetupLink, getTrackingCategory};
2 changes: 1 addition & 1 deletion src/pages/workspace/accounting/xero/XeroImportPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function XeroImportPage({policy}: WithPolicyProps) {
},
{
description: translate('workspace.xero.trackingCategories'),
action: () => {},
action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.getRoute(policyID)),
hasError: !!policy?.errors?.importTrackingCategories,
title: importTrackingCategories ? translate('workspace.accounting.importTypes.TAG') : translate('workspace.xero.notImported'),
pendingAction: pendingFields?.importTrackingCategories,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, {useCallback, useMemo} from 'react';
import ConnectionLayout from '@components/ConnectionLayout';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Connections from '@libs/actions/connections';
import {getTrackingCategory} from '@libs/actions/connections/ConnectToXero';
import Navigation from '@libs/Navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ROUTES from '@src/ROUTES';

function XeroMapCostCentersToConfigurationPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const policyID = policy?.id ?? '';

const category = getTrackingCategory(policy, CONST.XERO_CONFIG.TRACKING_CATEGORY_FIELDS.COST_CENTERS);

const optionsList = useMemo(
() =>
Object.values(CONST.XERO_CONFIG.TRACKING_CATEGORY_OPTIONS).map((option) => ({
value: option,
text: translate(`workspace.xero.trackingCategoriesOptions.${option.toLowerCase()}` as TranslationPaths),
keyForList: option,
isSelected: option.toLowerCase() === category?.value?.toLowerCase(),
})),
[translate, category],
);

const updateMapping = useCallback(
(option: {value: string}) => {
if (option.value !== category?.value) {
Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.MAPPINGS, {
...(policy?.connections?.xero?.config?.mappings ?? {}),
...(category?.id ? {[`${CONST.XERO_CONFIG.TRACKING_CATEGORY_PREFIX}${category.id}`]: option.value} : {}),
});
}
Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.getRoute(policyID));
},
[category, policyID, policy?.connections?.xero?.config?.mappings],
);

return (
<ConnectionLayout
displayName={XeroMapCostCentersToConfigurationPage.displayName}
headerTitle="workspace.xero.mapXeroCostCentersTo"
title="workspace.xero.mapXeroCostCentersToDescription"
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN]}
policyID={policyID && category?.id ? policyID : ''}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
titleStyle={[styles.pb2, styles.ph5]}
contentContainerStyle={[styles.flex1]}
shouldUseScrollView={false}
>
<SelectionList
sections={[{data: optionsList}]}
ListItem={RadioListItem}
onSelectRow={updateMapping}
/>
</ConnectionLayout>
);
}

XeroMapCostCentersToConfigurationPage.displayName = 'XeroMapCostCentersToConfigurationPage';
export default withPolicyConnections(XeroMapCostCentersToConfigurationPage);
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, {useCallback, useMemo} from 'react';
import ConnectionLayout from '@components/ConnectionLayout';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Connections from '@libs/actions/connections';
import {getTrackingCategory} from '@libs/actions/connections/ConnectToXero';
import Navigation from '@libs/Navigation/Navigation';
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ROUTES from '@src/ROUTES';

function XeroMapRegionsToConfigurationPage({policy}: WithPolicyProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const policyID = policy?.id ?? '';
const category = getTrackingCategory(policy, CONST.XERO_CONFIG.TRACKING_CATEGORY_FIELDS.REGION);

const optionsList = useMemo(
() =>
Object.values(CONST.XERO_CONFIG.TRACKING_CATEGORY_OPTIONS).map((option) => ({
value: option,
text: translate(`workspace.xero.trackingCategoriesOptions.${option.toLowerCase()}` as TranslationPaths),
keyForList: option,
isSelected: option.toLowerCase() === category?.value?.toLowerCase(),
})),
[translate, category],
);

const updateMapping = useCallback(
(option: {value: string}) => {
if (option.value !== category?.value) {
Connections.updatePolicyConnectionConfig(policyID, CONST.POLICY.CONNECTIONS.NAME.XERO, CONST.XERO_CONFIG.MAPPINGS, {
...(policy?.connections?.xero?.config?.mappings ?? {}),
...(category?.id ? {[`${CONST.XERO_CONFIG.TRACKING_CATEGORY_PREFIX}${category.id}`]: option.value} : {}),
});
}
Navigation.goBack(ROUTES.POLICY_ACCOUNTING_XERO_TRACKING_CATEGORIES.getRoute(policyID));
},
[category, policyID, policy?.connections?.xero?.config?.mappings],
);

return (
<ConnectionLayout
displayName={XeroMapRegionsToConfigurationPage.displayName}
headerTitle="workspace.xero.mapXeroRegionsTo"
title="workspace.xero.mapXeroRegionsToDescription"
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN]}
policyID={policyID && category?.id ? policyID : ''}
featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED}
titleStyle={[styles.pb2, styles.ph5]}
contentContainerStyle={[styles.flex1]}
shouldUseScrollView={false}
>
<SelectionList
sections={[{data: optionsList}]}
ListItem={RadioListItem}
onSelectRow={updateMapping}
/>
</ConnectionLayout>
);
}

XeroMapRegionsToConfigurationPage.displayName = 'XeroMapRegionsToConfigurationPage';
export default withPolicyConnections(XeroMapRegionsToConfigurationPage);
Loading

0 comments on commit e0b6a06

Please sign in to comment.