diff --git a/assets/images/product-illustrations/folder-with-papers.svg b/assets/images/product-illustrations/folder-with-papers.svg
new file mode 100644
index 000000000000..3d00fb147ccd
--- /dev/null
+++ b/assets/images/product-illustrations/folder-with-papers.svg
@@ -0,0 +1,33 @@
+
diff --git a/src/CONST.ts b/src/CONST.ts
index 6d81abc37a85..a872636eb427 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -609,6 +609,7 @@ const CONST = {
TRAVEL_TERMS_URL: `${USE_EXPENSIFY_URL}/travelterms`,
EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT: 'https://www.expensify.com/tools/integrations/downloadPackage',
EXPENSIFY_PACKAGE_FOR_SAGE_INTACCT_FILE_NAME: 'ExpensifyPackageForSageIntacct',
+ SAGE_INTACCT_INSTRUCTIONS: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct',
HOW_TO_CONNECT_TO_SAGE_INTACCT: 'https://help.expensify.com/articles/expensify-classic/integrations/accounting-integrations/Sage-Intacct#how-to-connect-to-sage-intacct',
PRICING: `https://www.expensify.com/pricing`,
@@ -1345,7 +1346,23 @@ const CONST = {
},
},
+ SAGE_INTACCT_MAPPING_VALUE: {
+ NONE: 'NONE',
+ DEFAULT: 'DEFAULT',
+ TAG: 'TAG',
+ REPORT_FIELD: 'REPORT_FIELD',
+ },
+
SAGE_INTACCT_CONFIG: {
+ MAPPINGS: {
+ DEPARTMENTS: 'departments',
+ CLASSES: 'classes',
+ LOCATIONS: 'locations',
+ CUSTOMERS: 'customers',
+ PROJECTS: 'projects',
+ },
+ SYNC_ITEMS: 'syncItems',
+ TAX: 'tax',
EXPORT: 'export',
EXPORT_DATE: 'exportDate',
NON_REIMBURSABLE_CREDIT_CARD_VENDOR: 'nonReimbursableCreditCardChargeDefaultVendor',
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index d5d3ac23a514..7f5ee1119bb1 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -564,6 +564,8 @@ const ONYXKEYS = {
NETSUITE_TOKEN_INPUT_FORM_DRAFT: 'netsuiteTokenInputFormDraft',
NETSUITE_CUSTOM_FORM_ID_FORM: 'netsuiteCustomFormIDForm',
NETSUITE_CUSTOM_FORM_ID_FORM_DRAFT: 'netsuiteCustomFormIDFormDraft',
+ SAGE_INTACCT_DIMENSION_TYPE_FORM: 'sageIntacctDimensionTypeForm',
+ SAGE_INTACCT_DIMENSION_TYPE_FORM_DRAFT: 'sageIntacctDimensionTypeFormDraft',
},
} as const;
@@ -629,6 +631,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.SAGE_INTACCT_CREDENTIALS_FORM]: FormTypes.SageIntactCredentialsForm;
[ONYXKEYS.FORMS.NETSUITE_TOKEN_INPUT_FORM]: FormTypes.NetSuiteTokenInputForm;
[ONYXKEYS.FORMS.NETSUITE_CUSTOM_FORM_ID_FORM]: FormTypes.NetSuiteCustomFormIDForm;
+ [ONYXKEYS.FORMS.SAGE_INTACCT_DIMENSION_TYPE_FORM]: FormTypes.SageIntacctDimensionForm;
};
type OnyxFormDraftValuesMapping = {
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 06f6291d2bee..3f93109463c9 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -3,6 +3,7 @@ import type CONST from './CONST';
import type {IOUAction, IOUType} from './CONST';
import type {IOURequestType} from './libs/actions/IOU';
import type {AuthScreensParamList} from './libs/Navigation/types';
+import type {SageIntacctMappingName} from './types/onyx/Policy';
import type {SearchQuery} from './types/onyx/SearchResults';
import type AssertTypesNotEqual from './types/utils/AssertTypesNotEqual';
@@ -1121,6 +1122,30 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/sage-intacct/existing-connections',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/existing-connections` as const,
},
+ POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT: {
+ route: 'settings/workspaces/:policyID/accounting/sage-intacct/import',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import` as const,
+ },
+ POLICY_ACCOUNTING_SAGE_INTACCT_TOGGLE_MAPPINGS: {
+ route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/toggle-mapping/:mapping',
+ getRoute: (policyID: string, mapping: SageIntacctMappingName) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/toggle-mapping/${mapping}` as const,
+ },
+ POLICY_ACCOUNTING_SAGE_INTACCT_MAPPINGS_TYPE: {
+ route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/mapping-type/:mapping',
+ getRoute: (policyID: string, mapping: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/mapping-type/${mapping}` as const,
+ },
+ POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS: {
+ route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/user-dimensions',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/user-dimensions` as const,
+ },
+ POLICY_ACCOUNTING_SAGE_INTACCT_ADD_USER_DIMENSION: {
+ route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/add-user-dimension',
+ getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/add-user-dimension` as const,
+ },
+ POLICY_ACCOUNTING_SAGE_INTACCT_EDIT_USER_DIMENSION: {
+ route: 'settings/workspaces/:policyID/accounting/sage-intacct/import/edit-user-dimension/:dimensionName',
+ getRoute: (policyID: string, dimensionName: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/import/edit-user-dimension/${dimensionName}` as const,
+ },
POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT: {
route: 'settings/workspaces/:policyID/accounting/sage-intacct/export',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/export` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 062f40ad251c..923164eff8c5 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -309,6 +309,12 @@ const SCREENS = {
SAGE_INTACCT_PREREQUISITES: 'Policy_Accounting_Sage_Intacct_Prerequisites',
ENTER_SAGE_INTACCT_CREDENTIALS: 'Policy_Enter_Sage_Intacct_Credentials',
EXISTING_SAGE_INTACCT_CONNECTIONS: 'Policy_Existing_Sage_Intacct_Connections',
+ SAGE_INTACCT_IMPORT: 'Policy_Accounting_Sage_Intacct_Import',
+ SAGE_INTACCT_TOGGLE_MAPPING: 'Policy_Accounting_Sage_Intacct_Toggle_Mapping',
+ SAGE_INTACCT_MAPPING_TYPE: 'Policy_Accounting_Sage_Intacct_Mapping_Type',
+ SAGE_INTACCT_USER_DIMENSIONS: 'Policy_Accounting_Sage_Intacct_User_Dimensions',
+ SAGE_INTACCT_ADD_USER_DIMENSION: 'Policy_Accounting_Sage_Intacct_Add_User_Dimension',
+ SAGE_INTACCT_EDIT_USER_DIMENSION: 'Policy_Accounting_Sage_Intacct_Edit_User_Dimension',
SAGE_INTACCT_EXPORT: 'Policy_Accounting_Sage_Intacct_Export',
SAGE_INTACCT_PREFERRED_EXPORTER: 'Policy_Accounting_Sage_Intacct_Preferred_Exporter',
SAGE_INTACCT_EXPORT_DATE: 'Policy_Accounting_Sage_Intacct_Export_Date',
diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts
index afbe2bb124b5..8cf59877c8db 100644
--- a/src/components/Form/types.ts
+++ b/src/components/Form/types.ts
@@ -20,6 +20,7 @@ import type TextInput from '@components/TextInput';
import type TextPicker from '@components/TextPicker';
import type ValuePicker from '@components/ValuePicker';
import type BusinessTypePicker from '@pages/ReimbursementAccount/BusinessInfo/substeps/TypeBusiness/BusinessTypePicker';
+import type DimensionTypeSelector from '@pages/workspace/accounting/intacct/import/DimensionTypeSelector';
import type {Country} from '@src/CONST';
import type {OnyxFormKey, OnyxValues} from '@src/ONYXKEYS';
import type {BaseForm} from '@src/types/form/Form';
@@ -39,6 +40,7 @@ type ValidInputs =
| typeof CurrencySelector
| typeof AmountForm
| typeof BusinessTypePicker
+ | typeof DimensionTypeSelector
| typeof StateSelector
| typeof RoomNameInput
| typeof ValuePicker
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 5212f5b0edb7..7a8186d2f38e 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -8,6 +8,7 @@ import ConciergeExclamation from '@assets/images/product-illustrations/concierge
import CreditCardsBlue from '@assets/images/product-illustrations/credit-cards--blue.svg';
import EmptyStateExpenses from '@assets/images/product-illustrations/emptystate__expenses.svg';
import EmptyStateTravel from '@assets/images/product-illustrations/emptystate__travel.svg';
+import FolderWithPapers from '@assets/images/product-illustrations/folder-with-papers.svg';
import GpsTrackOrange from '@assets/images/product-illustrations/gps-track--orange.svg';
import Hands from '@assets/images/product-illustrations/home-illustration-hands.svg';
import InvoiceOrange from '@assets/images/product-illustrations/invoice--orange.svg';
@@ -197,5 +198,6 @@ export {
CheckmarkCircle,
CreditCardEyes,
LockClosedOrange,
+ FolderWithPapers,
VirtualCard,
};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index d3cba1e83436..2074cca9cea4 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2,7 +2,7 @@ import {CONST as COMMON_CONST, Str} from 'expensify-common';
import {startCase} from 'lodash';
import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
-import type {ConnectionName, PolicyConnectionSyncStage} from '@src/types/onyx/Policy';
+import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy';
import type {
AddressLineParams,
AdminCanceledRequestParams,
@@ -2005,6 +2005,7 @@ export default {
categories: 'Categories',
tags: 'Tags',
reportFields: 'Report fields',
+ reportField: 'Report field',
taxes: 'Taxes',
bills: 'Bills',
invoices: 'Invoices',
@@ -2037,6 +2038,9 @@ export default {
welcomeNote: ({workspaceName}: WelcomeNoteParams) =>
`You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`,
subscription: 'Subscription',
+ lineItemLevel: 'Line-item level',
+ reportLevel: 'Report level',
+ appliedOnExport: 'Not imported into Expensify, applied on export',
},
qbo: {
importDescription: 'Choose which coding configurations to import from QuickBooks Online to Expensify.',
@@ -2506,6 +2510,42 @@ export default {
reuseExistingConnection: 'Reuse existing connection',
existingConnections: 'Existing connections',
sageIntacctLastSync: (formattedDate: string) => `Sage Intacct - Last synced ${formattedDate}`,
+ employeeDefault: 'Sage Intacct employee default',
+ employeeDefaultDescription: "The employee's default department will be applied to their expenses in Sage Intacct if one exists.",
+ displayedAsTagDescription: "Department will be selectable for each individual expense on an employee's report.",
+ displayedAsReportFieldDescription: "Department selection will apply to all expenses on an employee's report.",
+ toggleImportTitleFirstPart: 'Choose how to handle Sage Intacct ',
+ toggleImportTitleSecondPart: ' in Expensify.',
+ expenseTypes: 'Expense types',
+ expenseTypesDescription: 'Sage Intacct expense types import into Expensify as categories.',
+ importTaxDescription: 'Import purchase tax rate from Sage Intacct.',
+ userDefinedDimensions: 'User-defined dimensions',
+ addUserDefinedDimension: 'Add user-defined dimension',
+ integrationName: 'Integration name',
+ dimensionExists: 'A dimension with this name already exists.',
+ removeDimension: 'Remove user-defined dimension',
+ removeDimensionPrompt: 'Are you sure you want to remove this user-defined dimension?',
+ userDefinedDimension: 'User-defined dimension',
+ addAUserDefinedDimension: 'Add a user-defined dimension',
+ detailedInstructionsLink: 'View detailed instructions',
+ detailedInstructionsRestOfSentence: ' on adding user-defined dimensions.',
+ userDimensionsAdded: (dimensionsCount: number) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} added`,
+ mappingTitle: (mappingName: SageIntacctMappingName): string => {
+ switch (mappingName) {
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS:
+ return 'departments';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.CLASSES:
+ return 'classes';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.LOCATIONS:
+ return 'locations';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.CUSTOMERS:
+ return 'customers';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.PROJECTS:
+ return 'projects (jobs)';
+ default:
+ return 'mappings';
+ }
+ },
},
type: {
free: 'Free',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 5e5dcf0057a8..0e80136a8a95 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1,6 +1,6 @@
import {Str} from 'expensify-common';
import CONST from '@src/CONST';
-import type {ConnectionName, PolicyConnectionSyncStage} from '@src/types/onyx/Policy';
+import type {ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy';
import type {
AddressLineParams,
AdminCanceledRequestParams,
@@ -2068,6 +2068,10 @@ export default {
welcomeNote: ({workspaceName}: WelcomeNoteParams) =>
`¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`,
subscription: 'Suscripción',
+ reportField: 'Campo del informe',
+ lineItemLevel: 'Nivel de partida',
+ reportLevel: 'Nivel de informe',
+ appliedOnExport: 'No se importa en Expensify, se aplica en la exportación',
},
qbo: {
importDescription: 'Elige que configuraciónes de codificación son importadas desde QuickBooks Online a Expensify.',
@@ -2551,6 +2555,42 @@ export default {
reuseExistingConnection: 'Reutilizar la conexión existente',
existingConnections: 'Conexiones existentes',
sageIntacctLastSync: (formattedDate: string) => `Sage Intacct - Última sincronización ${formattedDate}`,
+ employeeDefault: 'Sage Intacct empleado por defecto',
+ employeeDefaultDescription: 'El departamento por defecto del empleado se aplicará a sus gastos en Sage Intacct si existe.',
+ displayedAsTagDescription: 'Se podrá seleccionar el departamento para cada gasto individual en el informe de un empleado.',
+ displayedAsReportFieldDescription: 'La selección de departamento se aplicará a todos los gastos que figuren en el informe de un empleado.',
+ toggleImportTitleFirstPart: 'Elija cómo gestionar Sage Intacct ',
+ toggleImportTitleSecondPart: ' en Expensify.',
+ expenseTypes: 'Tipos de gastos',
+ expenseTypesDescription: 'Los tipos de gastos de Sage Intacct se importan a Expensify como categorías.',
+ importTaxDescription: 'Importar el tipo impositivo de compra desde Sage Intacct.',
+ userDefinedDimensions: 'Dimensiones definidas por el usuario',
+ addUserDefinedDimension: 'Añadir dimensión definida por el usuario',
+ integrationName: 'Nombre de la integración',
+ dimensionExists: 'Ya existe una dimensión con ese nombre.',
+ removeDimension: 'Eliminar dimensión definida por el usuario',
+ removeDimensionPrompt: 'Está seguro de que desea eliminar esta dimensión definida por el usuario?',
+ userDefinedDimension: 'Dimensión definida por el usuario',
+ addAUserDefinedDimension: 'Añadir una dimensión definida por el usuario',
+ detailedInstructionsLink: 'Ver instrucciones detalladas',
+ detailedInstructionsRestOfSentence: ' para añadir dimensiones definidas por el usuario.',
+ userDimensionsAdded: (dimensionsCount: number) => `${dimensionsCount} ${Str.pluralize('UDD', `UDDs`, dimensionsCount)} añadido`,
+ mappingTitle: (mappingName: SageIntacctMappingName): string => {
+ switch (mappingName) {
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS:
+ return 'departamentos';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.CLASSES:
+ return 'clases';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.LOCATIONS:
+ return 'lugares';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.CUSTOMERS:
+ return 'clientes';
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.PROJECTS:
+ return 'proyectos (empleos)';
+ default:
+ return 'asignaciones';
+ }
+ },
},
type: {
free: 'Gratis',
diff --git a/src/libs/API/parameters/UpdateSageIntacctGenericParams.ts b/src/libs/API/parameters/UpdateSageIntacctGenericParams.ts
new file mode 100644
index 000000000000..5ac0ccc21218
--- /dev/null
+++ b/src/libs/API/parameters/UpdateSageIntacctGenericParams.ts
@@ -0,0 +1,7 @@
+type UpdateSageIntacctGenericParams = {
+ [K2 in K]: Type;
+} & {
+ policyID: string;
+};
+
+export default UpdateSageIntacctGenericParams;
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index b7b85f98b599..f599034d5773 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -1,5 +1,6 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
+import type {SageIntacctDimension, SageIntacctMappingValue} from '@src/types/onyx/Policy';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import type * as Parameters from './parameters';
import type SignInUserParams from './parameters/SignInUserParams';
@@ -287,6 +288,14 @@ const WRITE_COMMANDS = {
UPDATE_SAGE_INTACCT_SYNC_REIMBURSEMENT_ACCOUNT_ID: 'UpdateSageIntacctSyncReimbursementAccountID',
CONNECT_POLICY_TO_NETSUITE: 'ConnectPolicyToNetSuite',
CLEAR_OUTSTANDING_BALANCE: 'ClearOutstandingBalance',
+ UPDATE_SAGE_INTACCT_BILLABLE: 'UpdateSageIntacctBillable',
+ UPDATE_SAGE_INTACCT_DEPARTMENT_MAPPING: 'UpdateSageIntacctDepartmentsMapping',
+ UPDATE_SAGE_INTACCT_CLASSES_MAPPING: 'UpdateSageIntacctClassesMapping',
+ UPDATE_SAGE_INTACCT_LOCATIONS_MAPPING: 'UpdateSageIntacctLocationsMapping',
+ UPDATE_SAGE_INTACCT_CUSTOMERS_MAPPING: 'UpdateSageIntacctCustomersMapping',
+ UPDATE_SAGE_INTACCT_PROJECTS_MAPPING: 'UpdateSageIntacctProjectsMapping',
+ UPDATE_SAGE_INTACCT_SYNC_TAX_CONFIGURATION: 'UpdateSageIntacctSyncTaxConfiguration',
+ UPDATE_SAGE_INTACCT_USER_DIMENSION: 'UpdateSageIntacctUserDimension',
UPDATE_SAGE_INTACCT_EXPORTER: 'UpdateSageIntacctExporter',
UPDATE_SAGE_INTACCT_EXPORT_DATE: 'UpdateSageIntacctExportDate',
UPDATE_SAGE_INTACCT_REIMBURSABLE_EXPENSES_EXPORT_DESTINATION: 'UpdateSageIntacctReimbursableExpensesExportDestination',
@@ -597,6 +606,14 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_NETSUITE_APPROVAL_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'value', string>;
[WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_REIMBURSABLE]: Parameters.UpdateNetSuiteCustomFormIDParams;
[WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_NON_REIMBURSABLE]: Parameters.UpdateNetSuiteCustomFormIDParams;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_BILLABLE]: Parameters.UpdateSageIntacctGenericTypeParams<'enabled', boolean>;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_DEPARTMENT_MAPPING]: Parameters.UpdateSageIntacctGenericTypeParams<'mapping', SageIntacctMappingValue>;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_CLASSES_MAPPING]: Parameters.UpdateSageIntacctGenericTypeParams<'mapping', SageIntacctMappingValue>;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_LOCATIONS_MAPPING]: Parameters.UpdateSageIntacctGenericTypeParams<'mapping', SageIntacctMappingValue>;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_CUSTOMERS_MAPPING]: Parameters.UpdateSageIntacctGenericTypeParams<'mapping', SageIntacctMappingValue>;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_PROJECTS_MAPPING]: Parameters.UpdateSageIntacctGenericTypeParams<'mapping', SageIntacctMappingValue>;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_SYNC_TAX_CONFIGURATION]: Parameters.UpdateSageIntacctGenericTypeParams<'enabled', boolean>;
+ [WRITE_COMMANDS.UPDATE_SAGE_INTACCT_USER_DIMENSION]: Parameters.UpdateSageIntacctGenericTypeParams<'dimensions', SageIntacctDimension[]>;
};
const READ_COMMANDS = {
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index cff9ddf3ab39..6dc9e13d0500 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -367,7 +367,7 @@ const SettingsModalStackNavigator = createModalStackNavigator('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_CUSTOM_FORM_ID]: () => require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage').default,
- [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: () => require('../../../../pages/workspace/accounting/intacct/IntacctPrerequisitesPage').default,
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: () => require('../../../../pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS]: () =>
require('../../../../pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.EXISTING_SAGE_INTACCT_CONNECTIONS]: () => require('../../../../pages/workspace/accounting/intacct/ExistingConnectionsPage').default,
@@ -405,6 +405,17 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/reportFields/ReportFieldValueSettingsPage').default,
[SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE]: () => require('../../../../pages/workspace/reportFields/ReportFieldInitialValuePage').default,
[SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_VALUE]: () => require('../../../../pages/workspace/reportFields/ReportFieldEditValuePage').default,
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_IMPORT]: () => require('../../../../pages/workspace/accounting/intacct/import/SageIntacctImportPage').default,
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_TOGGLE_MAPPING]: () =>
+ require('../../../../pages/workspace/accounting/intacct/import/SageIntacctToggleMappingsPage').default,
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_MAPPING_TYPE]: () =>
+ require('../../../../pages/workspace/accounting/intacct/import/SageIntacctMappingsTypePage').default,
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_USER_DIMENSIONS]: () =>
+ require('../../../../pages/workspace/accounting/intacct/import/SageIntacctUserDimensionsPage').default,
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_ADD_USER_DIMENSION]: () =>
+ require('../../../../pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage').default,
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EDIT_USER_DIMENSION]: () =>
+ require('../../../../pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage').default,
});
const EnablePaymentsStackNavigator = createModalStackNavigator({
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 ee39f53dd040..1dec713058c4 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -92,6 +92,12 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES,
SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS,
SCREENS.WORKSPACE.ACCOUNTING.EXISTING_SAGE_INTACCT_CONNECTIONS,
+ SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_IMPORT,
+ SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_MAPPING_TYPE,
+ SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_TOGGLE_MAPPING,
+ SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_USER_DIMENSIONS,
+ SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_ADD_USER_DIMENSION,
+ SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EDIT_USER_DIMENSION,
SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EXPORT,
SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREFERRED_EXPORTER,
SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EXPORT_DATE,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 73d069eddb32..4dfe3df1df8c 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -425,6 +425,12 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.route},
[SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ENTER_CREDENTIALS.route},
[SCREENS.WORKSPACE.ACCOUNTING.EXISTING_SAGE_INTACCT_CONNECTIONS]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXISTING_CONNECTIONS.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_MAPPING_TYPE]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_MAPPINGS_TYPE.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_TOGGLE_MAPPING]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_TOGGLE_MAPPINGS.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_USER_DIMENSIONS]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_ADD_USER_DIMENSION]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ADD_USER_DIMENSION.route},
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EDIT_USER_DIMENSION]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EDIT_USER_DIMENSION.route},
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EXPORT]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT.route},
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREFERRED_EXPORTER]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREFERRED_EXPORTER.route},
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EXPORT_DATE]: {path: ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT_DATE.route},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 274c50d38b02..92263ab8d81e 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -19,6 +19,7 @@ import type NAVIGATORS from '@src/NAVIGATORS';
import type {HybridAppRoute, Route as Routes} from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type EXIT_SURVEY_REASON_FORM_INPUT_IDS from '@src/types/form/ExitSurveyReasonForm';
+import type {SageIntacctMappingName} from '@src/types/onyx/Policy';
type NavigationRef = NavigationContainerRefWithCurrent;
@@ -517,6 +518,27 @@ type SettingsNavigatorParamList = {
policyID: string;
expenseType: ValueOf;
};
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_IMPORT]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_TOGGLE_MAPPING]: {
+ policyID: string;
+ mapping: SageIntacctMappingName;
+ };
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_MAPPING_TYPE]: {
+ policyID: string;
+ mapping: SageIntacctMappingName;
+ };
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_ADD_USER_DIMENSION]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_USER_DIMENSIONS]: {
+ policyID: string;
+ };
+ [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EDIT_USER_DIMENSION]: {
+ policyID: string;
+ dimensionName: string;
+ };
[SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_EXPORT]: {
policyID: string;
};
diff --git a/src/libs/actions/connections/SageIntacct.ts b/src/libs/actions/connections/SageIntacct.ts
index 1058287f9f3b..75a8b1e2d555 100644
--- a/src/libs/actions/connections/SageIntacct.ts
+++ b/src/libs/actions/connections/SageIntacct.ts
@@ -2,12 +2,20 @@ import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import * as API from '@libs/API';
-import type ConnectPolicyToSageIntacctParams from '@libs/API/parameters/ConnectPolicyToSageIntacctParams';
+import type {ConnectPolicyToSageIntacctParams} from '@libs/API/parameters';
import {WRITE_COMMANDS} from '@libs/API/types';
import * as ErrorUtils from '@libs/ErrorUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Connections, SageIntacctConnectionsConfig} from '@src/types/onyx/Policy';
+import type {
+ Connections,
+ SageIntacctConnectionsConfig,
+ SageIntacctDimension,
+ SageIntacctMappingName,
+ SageIntacctMappingType,
+ SageIntacctMappingValue,
+ SageIntacctOfflineStateKeys,
+} from '@src/types/onyx/Policy';
type SageIntacctCredentials = {companyID: string; userID: string; password: string};
@@ -21,7 +29,7 @@ function connectToSageIntacct(policyID: string, credentials: SageIntacctCredenti
API.write(WRITE_COMMANDS.CONNECT_POLICY_TO_SAGE_INTACCT, parameters, {});
}
-function prepareOnyxDataForConfigUpdate(policyID: string, settingName: keyof SageIntacctConnectionsConfig, settingValue: string | boolean | null) {
+function prepareOnyxDataForMappingUpdate(policyID: string, mappingName: keyof SageIntacctMappingType, mappingValue: boolean | SageIntacctMappingValue) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
@@ -30,12 +38,14 @@ function prepareOnyxDataForConfigUpdate(policyID: string, settingName: keyof Sag
connections: {
intacct: {
config: {
- [settingName]: settingValue,
+ mappings: {
+ [mappingName]: mappingValue,
+ },
pendingFields: {
- [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ [mappingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errorFields: {
- [settingName]: null,
+ [mappingName]: null,
},
},
},
@@ -53,10 +63,10 @@ function prepareOnyxDataForConfigUpdate(policyID: string, settingName: keyof Sag
intacct: {
config: {
pendingFields: {
- [settingName]: null,
+ [mappingName]: null,
},
errorFields: {
- [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
+ [mappingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
},
},
},
@@ -74,10 +84,10 @@ function prepareOnyxDataForConfigUpdate(policyID: string, settingName: keyof Sag
intacct: {
config: {
pendingFields: {
- [settingName]: null,
+ [mappingName]: null,
},
errorFields: {
- [settingName]: null,
+ [mappingName]: undefined,
},
},
},
@@ -89,7 +99,49 @@ function prepareOnyxDataForConfigUpdate(policyID: string, settingName: keyof Sag
return {optimisticData, failureData, successData};
}
-function prepareOnyxDataForSyncUpdate(policyID: string, settingName: keyof Connections['intacct']['config']['sync'], settingValue: string | boolean) {
+function updateSageIntacctBillable(policyID: string, enabled: boolean) {
+ const parameters = {
+ policyID,
+ enabled,
+ };
+ API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_BILLABLE, parameters, prepareOnyxDataForMappingUpdate(policyID, CONST.SAGE_INTACCT_CONFIG.SYNC_ITEMS, enabled));
+}
+
+function getCommandForMapping(mappingName: ValueOf) {
+ switch (mappingName) {
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.DEPARTMENTS:
+ return WRITE_COMMANDS.UPDATE_SAGE_INTACCT_DEPARTMENT_MAPPING;
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.CLASSES:
+ return WRITE_COMMANDS.UPDATE_SAGE_INTACCT_CLASSES_MAPPING;
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.LOCATIONS:
+ return WRITE_COMMANDS.UPDATE_SAGE_INTACCT_LOCATIONS_MAPPING;
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.CUSTOMERS:
+ return WRITE_COMMANDS.UPDATE_SAGE_INTACCT_CUSTOMERS_MAPPING;
+ case CONST.SAGE_INTACCT_CONFIG.MAPPINGS.PROJECTS:
+ return WRITE_COMMANDS.UPDATE_SAGE_INTACCT_PROJECTS_MAPPING;
+ default:
+ return undefined;
+ }
+}
+
+function updateSageIntacctMappingValue(policyID: string, mappingName: SageIntacctMappingName, mappingValue: SageIntacctMappingValue) {
+ const command = getCommandForMapping(mappingName);
+ if (!command) {
+ return;
+ }
+
+ const onyxData = prepareOnyxDataForMappingUpdate(policyID, mappingName, mappingValue);
+ API.write(
+ command,
+ {
+ policyID,
+ mapping: mappingValue,
+ },
+ onyxData,
+ );
+}
+
+function updateSageIntacctSyncTaxConfiguration(policyID: string, enabled: boolean) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
@@ -98,14 +150,14 @@ function prepareOnyxDataForSyncUpdate(policyID: string, settingName: keyof Conne
connections: {
intacct: {
config: {
- sync: {
- [settingName]: settingValue,
+ tax: {
+ syncTax: enabled,
},
pendingFields: {
- [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ tax: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
errorFields: {
- [settingName]: null,
+ tax: null,
},
},
},
@@ -123,10 +175,10 @@ function prepareOnyxDataForSyncUpdate(policyID: string, settingName: keyof Conne
intacct: {
config: {
pendingFields: {
- [settingName]: null,
+ tax: null,
},
errorFields: {
- [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
+ tax: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
},
},
},
@@ -144,10 +196,10 @@ function prepareOnyxDataForSyncUpdate(policyID: string, settingName: keyof Conne
intacct: {
config: {
pendingFields: {
- [settingName]: null,
+ tax: null,
},
errorFields: {
- [settingName]: null,
+ tax: undefined,
},
},
},
@@ -155,11 +207,10 @@ function prepareOnyxDataForSyncUpdate(policyID: string, settingName: keyof Conne
},
},
];
-
- return {optimisticData, failureData, successData};
+ API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_SYNC_TAX_CONFIGURATION, {policyID, enabled}, {optimisticData, failureData, successData});
}
-function prepareOnyxDataForAutoSyncUpdate(policyID: string, settingName: keyof Connections['intacct']['config']['autoSync'], settingValue: boolean) {
+function prepareOnyxDataForUserDimensionUpdate(policyID: string, dimensionName: string, newDimensions: SageIntacctDimension[]) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
@@ -168,15 +219,11 @@ function prepareOnyxDataForAutoSyncUpdate(policyID: string, settingName: keyof C
connections: {
intacct: {
config: {
- autoSync: {
- [settingName]: settingValue,
- },
- pendingFields: {
- [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
- },
- errorFields: {
- [settingName]: null,
+ mappings: {
+ dimensions: newDimensions,
},
+ pendingFields: {[`dimension_${dimensionName}`]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
+ errorFields: {[`dimension_${dimensionName}`]: null},
},
},
},
@@ -192,12 +239,11 @@ function prepareOnyxDataForAutoSyncUpdate(policyID: string, settingName: keyof C
connections: {
intacct: {
config: {
- pendingFields: {
- [settingName]: null,
- },
- errorFields: {
- [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
+ mappings: {
+ dimensions: newDimensions,
},
+ pendingFields: {[`dimension_${dimensionName}`]: null},
+ errorFields: {[`dimension_${dimensionName}`]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')},
},
},
},
@@ -213,12 +259,11 @@ function prepareOnyxDataForAutoSyncUpdate(policyID: string, settingName: keyof C
connections: {
intacct: {
config: {
- pendingFields: {
- [settingName]: null,
- },
- errorFields: {
- [settingName]: null,
+ mappings: {
+ dimensions: newDimensions,
},
+ pendingFields: {[`dimension_${dimensionName}`]: null},
+ errorFields: {[`dimension_${dimensionName}`]: null},
},
},
},
@@ -229,6 +274,40 @@ function prepareOnyxDataForAutoSyncUpdate(policyID: string, settingName: keyof C
return {optimisticData, failureData, successData};
}
+function addSageIntacctUserDimensions(
+ policyID: string,
+ dimensionName: string,
+ mapping: typeof CONST.SAGE_INTACCT_MAPPING_VALUE.TAG | typeof CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ existingUserDimensions: SageIntacctDimension[],
+) {
+ const newDimensions = [...existingUserDimensions, {mapping, dimension: dimensionName}];
+
+ API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_USER_DIMENSION, {policyID, dimensions: newDimensions}, prepareOnyxDataForUserDimensionUpdate(policyID, dimensionName, newDimensions));
+}
+
+function editSageIntacctUserDimensions(
+ policyID: string,
+ previousName: string,
+ name: string,
+ mapping: typeof CONST.SAGE_INTACCT_MAPPING_VALUE.TAG | typeof CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ existingUserDimensions: SageIntacctDimension[],
+) {
+ const newDimensions = existingUserDimensions.map((userDimension) => {
+ if (userDimension.dimension === previousName) {
+ return {dimension: name, mapping};
+ }
+ return userDimension;
+ });
+
+ API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_USER_DIMENSION, {policyID, dimensions: newDimensions}, prepareOnyxDataForUserDimensionUpdate(policyID, name, newDimensions));
+}
+
+function removeSageIntacctUserDimensions(policyID: string, dimensionName: string, existingUserDimensions: SageIntacctDimension[]) {
+ const newDimensions = existingUserDimensions.filter((userDimension) => dimensionName !== userDimension.dimension);
+
+ API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_USER_DIMENSION, {policyID, dimensions: newDimensions}, prepareOnyxDataForUserDimensionUpdate(policyID, dimensionName, newDimensions));
+}
+
function prepareOnyxDataForExportUpdate(policyID: string, settingName: keyof Connections['intacct']['config']['export'], settingValue: string | null) {
const optimisticData: OnyxUpdate[] = [
{
@@ -395,6 +474,218 @@ function updateSageIntacctDefaultVendor(policyID: string, settingName: keyof Con
}
}
+function clearSageIntacctErrorField(policyID: string, key: SageIntacctOfflineStateKeys | keyof SageIntacctConnectionsConfig) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {intacct: {config: {errorFields: {[key]: null}}}}});
+}
+
+function prepareOnyxDataForConfigUpdate(policyID: string, settingName: keyof SageIntacctConnectionsConfig, settingValue: string | boolean | null) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ [settingName]: settingValue,
+ pendingFields: {
+ [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ return {optimisticData, failureData, successData};
+}
+
+function prepareOnyxDataForSyncUpdate(policyID: string, settingName: keyof Connections['intacct']['config']['sync'], settingValue: string | boolean) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ sync: {
+ [settingName]: settingValue,
+ },
+ pendingFields: {
+ [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ return {optimisticData, failureData, successData};
+}
+
+function prepareOnyxDataForAutoSyncUpdate(policyID: string, settingName: keyof Connections['intacct']['config']['autoSync'], settingValue: boolean) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ autoSync: {
+ [settingName]: settingValue,
+ },
+ pendingFields: {
+ [settingName]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'),
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ connections: {
+ intacct: {
+ config: {
+ pendingFields: {
+ [settingName]: null,
+ },
+ errorFields: {
+ [settingName]: null,
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+
+ return {optimisticData, failureData, successData};
+}
+
function updateSageIntacctAutoSync(policyID: string, enabled: boolean) {
const {optimisticData, failureData, successData} = prepareOnyxDataForAutoSyncUpdate(policyID, CONST.SAGE_INTACCT_CONFIG.AUTO_SYNC_ENABLED, enabled);
const parameters = {
@@ -448,7 +739,14 @@ function updateSageIntacctSyncReimbursementAccountID(policyID: string, vendorID:
export {
connectToSageIntacct,
+ updateSageIntacctBillable,
+ updateSageIntacctSyncTaxConfiguration,
+ addSageIntacctUserDimensions,
+ updateSageIntacctMappingValue,
+ editSageIntacctUserDimensions,
+ removeSageIntacctUserDimensions,
updateSageIntacctExporter,
+ clearSageIntacctErrorField,
updateSageIntacctExportDate,
updateSageIntacctReimbursableExpensesExportDestination,
updateSageIntacctNonreimbursableExpensesExportDestination,
diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
index 13bccf152c06..2c7b93eca0db 100644
--- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx
+++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx
@@ -128,7 +128,7 @@ function accountingIntegrationData(
integrationToDisconnect={integrationToDisconnect}
/>
),
- onImportPagePress: () => {},
+ onImportPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT.getRoute(policyID)),
onExportPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXPORT.getRoute(policyID)),
onAdvancedPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED.getRoute(policyID)),
};
diff --git a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx
index cc997123a412..fa7fdbfa1fc1 100644
--- a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx
+++ b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx
@@ -20,9 +20,9 @@ import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import INPUT_IDS from '@src/types/form/SageIntactCredentialsForm';
-type IntacctPrerequisitesPageProps = StackScreenProps;
+type SageIntacctPrerequisitesPageProps = StackScreenProps;
-function EnterSageIntacctCredentialsPage({route}: IntacctPrerequisitesPageProps) {
+function EnterSageIntacctCredentialsPage({route}: SageIntacctPrerequisitesPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const policyID: string = route.params.policyID;
@@ -93,6 +93,6 @@ function EnterSageIntacctCredentialsPage({route}: IntacctPrerequisitesPageProps)
);
}
-EnterSageIntacctCredentialsPage.displayName = 'PolicyEnterSageIntacctCredentialsPage';
+EnterSageIntacctCredentialsPage.displayName = 'EnterSageIntacctCredentialsPage';
export default EnterSageIntacctCredentialsPage;
diff --git a/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx b/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx
index 7e867d88285d..eaa63bbcaadb 100644
--- a/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx
+++ b/src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx
@@ -59,6 +59,6 @@ function ExistingConnectionsPage({route}: ExistingConnectionsPageProps) {
);
}
-ExistingConnectionsPage.displayName = 'PolicyExistingConnectionsPage';
+ExistingConnectionsPage.displayName = 'ExistingConnectionsPage';
export default ExistingConnectionsPage;
diff --git a/src/pages/workspace/accounting/intacct/IntacctPrerequisitesPage.tsx b/src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx
similarity index 91%
rename from src/pages/workspace/accounting/intacct/IntacctPrerequisitesPage.tsx
rename to src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx
index eaf46392fb04..8e921ca934b2 100644
--- a/src/pages/workspace/accounting/intacct/IntacctPrerequisitesPage.tsx
+++ b/src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx
@@ -23,9 +23,9 @@ import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
-type IntacctPrerequisitesPageProps = StackScreenProps;
+type SageIntacctPrerequisitesPageProps = StackScreenProps;
-function IntacctPrerequisitesPage({route}: IntacctPrerequisitesPageProps) {
+function SageIntacctPrerequisitesPage({route}: SageIntacctPrerequisitesPageProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const popoverAnchor = useRef(null);
@@ -67,7 +67,7 @@ function IntacctPrerequisitesPage({route}: IntacctPrerequisitesPageProps) {
void;
+};
+
+function DimensionTypeSelector({errorText = '', value = '', onInputChange}: DimensionTypeSelectorProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+
+ const selectionOptions = [
+ {
+ value: CONST.SAGE_INTACCT_MAPPING_VALUE.TAG,
+ text: translate('common.tag'),
+ alternateText: translate('workspace.common.lineItemLevel'),
+ keyForList: CONST.SAGE_INTACCT_MAPPING_VALUE.TAG,
+ isSelected: value === CONST.SAGE_INTACCT_MAPPING_VALUE.TAG,
+ },
+ {
+ value: CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ text: translate('workspace.common.reportField'),
+ alternateText: translate('workspace.common.reportLevel'),
+ keyForList: CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ isSelected: value === CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ },
+ ];
+
+ const onDimensionTypeSelected = (dimensionType: SelectorType) => {
+ if (!onInputChange || dimensionType.value === value) {
+ return;
+ }
+ onInputChange(dimensionType.value);
+ };
+
+ return (
+
+ {translate('workspace.common.displayedAs')}
+
+ {selectionOptions.map((option) => (
+
+ ))}
+ {!!errorText && (
+
+ )}
+
+
+ );
+}
+
+export default DimensionTypeSelector;
diff --git a/src/pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage.tsx b/src/pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage.tsx
new file mode 100644
index 000000000000..3dc7a325650b
--- /dev/null
+++ b/src/pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage.tsx
@@ -0,0 +1,101 @@
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import ConnectionLayout from '@components/ConnectionLayout';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
+import TextInput from '@components/TextInput';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {addSageIntacctUserDimensions} from '@libs/actions/connections/SageIntacct';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/SageIntacctDimensionsForm';
+import DimensionTypeSelector from './DimensionTypeSelector';
+
+function SageIntacctAddUserDimensionPage({policy}: WithPolicyProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ const policyID = policy?.id ?? '-1';
+ const userDimensions = policy?.connections?.intacct?.config?.mappings?.dimensions;
+ const {inputCallbackRef} = useAutoFocusInput();
+
+ const validate = useCallback(
+ (values: FormOnyxValues) => {
+ const errors: FormInputErrors = {};
+
+ if (!values[INPUT_IDS.INTEGRATION_NAME]) {
+ ErrorUtils.addErrorMessage(errors, INPUT_IDS.INTEGRATION_NAME, translate('common.error.fieldRequired'));
+ }
+
+ if (userDimensions?.some((userDimension) => userDimension.dimension === values[INPUT_IDS.INTEGRATION_NAME])) {
+ ErrorUtils.addErrorMessage(errors, INPUT_IDS.INTEGRATION_NAME, translate('workspace.intacct.dimensionExists'));
+ }
+
+ if (!values[INPUT_IDS.DIMENSION_TYPE]) {
+ ErrorUtils.addErrorMessage(errors, INPUT_IDS.DIMENSION_TYPE, translate('common.error.fieldRequired'));
+ }
+ return errors;
+ },
+ [translate, userDimensions],
+ );
+
+ return (
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS.getRoute(policyID))}
+ >
+ {
+ addSageIntacctUserDimensions(policyID, value[INPUT_IDS.INTEGRATION_NAME], value[INPUT_IDS.DIMENSION_TYPE], userDimensions ?? []);
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS.getRoute(policyID));
+ }}
+ submitButtonText={translate('common.confirm')}
+ enabledWhenOffline
+ shouldValidateOnBlur
+ shouldValidateOnChange
+ >
+
+
+
+
+
+
+
+
+ );
+}
+
+SageIntacctAddUserDimensionPage.displayName = 'SageIntacctAddUserDimensionPage';
+
+export default withPolicy(SageIntacctAddUserDimensionPage);
diff --git a/src/pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage.tsx b/src/pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage.tsx
new file mode 100644
index 000000000000..d0d9578a72b2
--- /dev/null
+++ b/src/pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage.tsx
@@ -0,0 +1,141 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useCallback, useState} from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import ConfirmModal from '@components/ConfirmModal';
+import ConnectionLayout from '@components/ConnectionLayout';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
+import * as Expensicons from '@components/Icon/Expensicons';
+import MenuItem from '@components/MenuItem';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import TextInput from '@components/TextInput';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {clearSageIntacctErrorField, editSageIntacctUserDimensions, removeSageIntacctUserDimensions} from '@libs/actions/connections/SageIntacct';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import INPUT_IDS from '@src/types/form/SageIntacctDimensionsForm';
+import DimensionTypeSelector from './DimensionTypeSelector';
+
+type SageIntacctEditUserDimensionsPageProps = StackScreenProps;
+
+function SageIntacctEditUserDimensionsPage({route}: SageIntacctEditUserDimensionsPageProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ const editedUserDimensionName: string = route.params.dimensionName;
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID ?? '-1'}`);
+ const policyID: string = policy?.id ?? '-1';
+ const config = policy?.connections?.intacct?.config;
+ const userDimensions = policy?.connections?.intacct?.config?.mappings?.dimensions;
+ const editedUserDimension = userDimensions?.find((userDimension) => userDimension.dimension === editedUserDimensionName);
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+
+ const validate = useCallback(
+ (values: FormOnyxValues) => {
+ const errors: FormInputErrors = {};
+
+ if (!values[INPUT_IDS.INTEGRATION_NAME]) {
+ ErrorUtils.addErrorMessage(errors, INPUT_IDS.INTEGRATION_NAME, translate('common.error.fieldRequired'));
+ }
+
+ if (userDimensions?.some((userDimension) => userDimension.dimension === values[INPUT_IDS.INTEGRATION_NAME] && editedUserDimensionName !== values[INPUT_IDS.INTEGRATION_NAME])) {
+ ErrorUtils.addErrorMessage(errors, INPUT_IDS.INTEGRATION_NAME, translate('workspace.intacct.dimensionExists'));
+ }
+
+ if (!values[INPUT_IDS.DIMENSION_TYPE]) {
+ ErrorUtils.addErrorMessage(errors, INPUT_IDS.DIMENSION_TYPE, translate('common.error.fieldRequired'));
+ }
+ return errors;
+ },
+ [editedUserDimensionName, translate, userDimensions],
+ );
+
+ return (
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS.getRoute(policyID))}
+ >
+ {
+ editSageIntacctUserDimensions(policyID, editedUserDimensionName, value[INPUT_IDS.INTEGRATION_NAME], value[INPUT_IDS.DIMENSION_TYPE], userDimensions ?? []);
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS.getRoute(policyID));
+ }}
+ submitButtonText={translate('common.save')}
+ enabledWhenOffline
+ shouldValidateOnBlur
+ shouldValidateOnChange
+ >
+ clearSageIntacctErrorField(policyID, `dimension_${editedUserDimensionName}`)}
+ >
+
+
+
+
+
+
+
+
+
+ {
+ setIsDeleteModalOpen(false);
+ removeSageIntacctUserDimensions(policyID, editedUserDimensionName, userDimensions ?? []);
+ Navigation.goBack();
+ }}
+ onCancel={() => setIsDeleteModalOpen(false)}
+ prompt={translate('workspace.intacct.removeDimensionPrompt')}
+ confirmText={translate('common.remove')}
+ cancelText={translate('common.cancel')}
+ danger
+ shouldEnableNewFocusManagement
+ />
+
+
+ );
+}
+
+SageIntacctEditUserDimensionsPage.displayName = 'SageIntacctEditUserDimensionsPage';
+
+export default SageIntacctEditUserDimensionsPage;
diff --git a/src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx b/src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx
new file mode 100644
index 000000000000..22dfc25c7dda
--- /dev/null
+++ b/src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx
@@ -0,0 +1,144 @@
+import {Str} from 'expensify-common';
+import React, {useMemo} from 'react';
+import ConnectionLayout from '@components/ConnectionLayout';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {clearSageIntacctErrorField, updateSageIntacctBillable, updateSageIntacctSyncTaxConfiguration} from '@libs/actions/connections/SageIntacct';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
+import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
+import ROUTES from '@src/ROUTES';
+import type {SageIntacctConnectionsConfig, SageIntacctMappingValue} from '@src/types/onyx/Policy';
+
+function getDisplayTypeTranslationKey(displayType?: SageIntacctMappingValue): TranslationPaths | undefined {
+ switch (displayType) {
+ case CONST.SAGE_INTACCT_MAPPING_VALUE.DEFAULT: {
+ return 'workspace.intacct.employeeDefault';
+ }
+ case CONST.SAGE_INTACCT_MAPPING_VALUE.TAG: {
+ return 'workspace.accounting.importTypes.TAG';
+ }
+ case CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD: {
+ return 'workspace.accounting.importTypes.REPORT_FIELD';
+ }
+ default: {
+ return 'workspace.accounting.notImported';
+ }
+ }
+}
+
+const checkForUserDimensionWithError = (config?: SageIntacctConnectionsConfig) =>
+ config?.mappings?.dimensions?.some((dimension) => !!config?.errorFields?.[`dimension_${dimension.dimension}`]);
+
+const checkForUserDimensionWithPendingAction = (config?: SageIntacctConnectionsConfig) =>
+ config?.mappings?.dimensions?.some((dimension) => !!config?.pendingFields?.[`dimension_${dimension.dimension}`]);
+
+function SageIntacctImportPage({policy}: WithPolicyProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+
+ const policyID: string = policy?.id ?? '-1';
+ const sageIntacctConfig = policy?.connections?.intacct?.config;
+
+ const mapingItems = useMemo(
+ () =>
+ Object.values(CONST.SAGE_INTACCT_CONFIG.MAPPINGS).map((mapping) => {
+ const menuItemTitleKey = getDisplayTypeTranslationKey(sageIntacctConfig?.mappings?.[mapping]);
+ return {
+ description: Str.recapitalize(translate('workspace.intacct.mappingTitle', mapping)),
+ action: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_TOGGLE_MAPPINGS.getRoute(policyID, mapping)),
+ title: menuItemTitleKey ? translate(menuItemTitleKey) : undefined,
+ hasError: !!sageIntacctConfig?.errorFields?.[mapping],
+ pendingAction: sageIntacctConfig?.pendingFields?.[mapping],
+ };
+ }),
+ [policyID, sageIntacctConfig?.errorFields, sageIntacctConfig?.mappings, sageIntacctConfig?.pendingFields, translate],
+ );
+
+ return (
+
+ {}}
+ disabled
+ />
+ updateSageIntacctBillable(policyID, !sageIntacctConfig?.mappings?.syncItems)}
+ pendingAction={sageIntacctConfig?.pendingFields?.syncItems}
+ errors={ErrorUtils.getLatestErrorField(sageIntacctConfig ?? {}, CONST.SAGE_INTACCT_CONFIG.SYNC_ITEMS)}
+ onCloseError={() => clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.SYNC_ITEMS)}
+ />
+
+ {mapingItems.map((section) => (
+
+
+
+ ))}
+
+ updateSageIntacctSyncTaxConfiguration(policyID, !sageIntacctConfig?.tax?.syncTax)}
+ pendingAction={sageIntacctConfig?.pendingFields?.tax}
+ errors={ErrorUtils.getLatestErrorField(sageIntacctConfig ?? {}, CONST.SAGE_INTACCT_CONFIG.TAX)}
+ onCloseError={() => clearSageIntacctErrorField(policyID, CONST.SAGE_INTACCT_CONFIG.TAX)}
+ />
+
+
+ 0
+ ? translate('workspace.intacct.userDimensionsAdded', sageIntacctConfig?.mappings?.dimensions?.length)
+ : undefined
+ }
+ description={translate('workspace.intacct.userDefinedDimensions')}
+ shouldShowRightIcon
+ onPress={() => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_USER_DIMENSIONS.getRoute(policyID))}
+ brickRoadIndicator={checkForUserDimensionWithError(sageIntacctConfig) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
+ />
+
+
+ );
+}
+
+SageIntacctImportPage.displayName = 'SageIntacctImportPage';
+
+export default withPolicy(SageIntacctImportPage);
diff --git a/src/pages/workspace/accounting/intacct/import/SageIntacctMappingsTypePage.tsx b/src/pages/workspace/accounting/intacct/import/SageIntacctMappingsTypePage.tsx
new file mode 100644
index 000000000000..57ebac617393
--- /dev/null
+++ b/src/pages/workspace/accounting/intacct/import/SageIntacctMappingsTypePage.tsx
@@ -0,0 +1,81 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useCallback, useMemo} from 'react';
+import {useOnyx} from 'react-native-onyx';
+import RadioListItem from '@components/SelectionList/RadioListItem';
+import SelectionScreen from '@components/SelectionScreen';
+import type {SelectorType} from '@components/SelectionScreen';
+import useLocalize from '@hooks/useLocalize';
+import {updateSageIntacctMappingValue} from '@libs/actions/connections/SageIntacct';
+import Navigation from '@libs/Navigation/Navigation';
+import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {SageIntacctMappingName, SageIntacctMappingValue} from '@src/types/onyx/Policy';
+
+type SageIntacctMappingsTypePageProps = StackScreenProps;
+
+function SageIntacctMappingsTypePage({route}: SageIntacctMappingsTypePageProps) {
+ const {translate} = useLocalize();
+
+ const mappingName: SageIntacctMappingName = route.params.mapping;
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID ?? '-1'}`);
+ const policyID = policy?.id ?? '-1';
+ const mappings = policy?.connections?.intacct?.config?.mappings;
+
+ const selectionOptions = useMemo(
+ () => [
+ {
+ value: CONST.SAGE_INTACCT_MAPPING_VALUE.DEFAULT,
+ text: translate('workspace.intacct.employeeDefault'),
+ alternateText: translate('workspace.common.appliedOnExport'),
+ keyForList: CONST.SAGE_INTACCT_MAPPING_VALUE.DEFAULT,
+ isSelected: mappings?.[mappingName] === CONST.SAGE_INTACCT_MAPPING_VALUE.DEFAULT,
+ },
+ {
+ value: CONST.SAGE_INTACCT_MAPPING_VALUE.TAG,
+ text: translate('workspace.common.tags'),
+ alternateText: translate('workspace.common.lineItemLevel'),
+ keyForList: CONST.SAGE_INTACCT_MAPPING_VALUE.TAG,
+ isSelected: mappings?.[mappingName] === CONST.SAGE_INTACCT_MAPPING_VALUE.TAG,
+ },
+ {
+ value: CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ text: translate('workspace.common.reportFields'),
+ alternateText: translate('workspace.common.reportLevel'),
+ keyForList: CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ isSelected: mappings?.[mappingName] === CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD,
+ },
+ ],
+ [mappingName, mappings, translate],
+ );
+
+ const updateMapping = useCallback(
+ ({value}: SelectorType) => {
+ updateSageIntacctMappingValue(policyID, mappingName, value as SageIntacctMappingValue);
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_TOGGLE_MAPPINGS.getRoute(policyID, mappingName));
+ },
+ [mappingName, policyID],
+ );
+
+ return (
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_TOGGLE_MAPPINGS.getRoute(policyID, mappingName))}
+ title="workspace.common.displayedAs"
+ />
+ );
+}
+
+SageIntacctMappingsTypePage.displayName = 'SageIntacctMappingsTypePage';
+
+export default SageIntacctMappingsTypePage;
diff --git a/src/pages/workspace/accounting/intacct/import/SageIntacctToggleMappingsPage.tsx b/src/pages/workspace/accounting/intacct/import/SageIntacctToggleMappingsPage.tsx
new file mode 100644
index 000000000000..c76a9f0e26bc
--- /dev/null
+++ b/src/pages/workspace/accounting/intacct/import/SageIntacctToggleMappingsPage.tsx
@@ -0,0 +1,122 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import {Str} from 'expensify-common';
+import React, {useState} from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import ConnectionLayout from '@components/ConnectionLayout';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {clearSageIntacctErrorField, updateSageIntacctMappingValue} from '@libs/actions/connections/SageIntacct';
+import * as ErrorUtils from '@libs/ErrorUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
+import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {SageIntacctMappingName, SageIntacctMappingValue} from '@src/types/onyx/Policy';
+
+type SageIntacctToggleMappingsPageProps = StackScreenProps;
+
+type DisplayTypeTranslationKeys = {
+ titleKey: TranslationPaths;
+ descriptionKey: TranslationPaths;
+};
+
+function getDisplayTypeTranslationKeys(displayType?: SageIntacctMappingValue): DisplayTypeTranslationKeys | undefined {
+ switch (displayType) {
+ case CONST.SAGE_INTACCT_MAPPING_VALUE.DEFAULT: {
+ return {titleKey: 'workspace.intacct.employeeDefault', descriptionKey: 'workspace.intacct.employeeDefaultDescription'};
+ }
+ case CONST.SAGE_INTACCT_MAPPING_VALUE.TAG: {
+ return {titleKey: 'workspace.common.tags', descriptionKey: 'workspace.intacct.displayedAsTagDescription'};
+ }
+ case CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD: {
+ return {titleKey: 'workspace.common.reportFields', descriptionKey: 'workspace.intacct.displayedAsReportFieldDescription'};
+ }
+ default: {
+ return undefined;
+ }
+ }
+}
+
+function SageIntacctToggleMappingsPage({route}: SageIntacctToggleMappingsPageProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID ?? '-1'}`);
+ const mappingName: SageIntacctMappingName = route.params.mapping;
+ const policyID: string = policy?.id ?? '-1';
+
+ const config = policy?.connections?.intacct?.config;
+ const translationKeys = getDisplayTypeTranslationKeys(config?.mappings?.[mappingName]);
+ const [importMapping, setImportMapping] = useState(config?.mappings?.[mappingName] && config?.mappings?.[mappingName] !== CONST.SAGE_INTACCT_MAPPING_VALUE.NONE);
+
+ return (
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT.getRoute(policyID))}
+ >
+
+ {translate('workspace.intacct.toggleImportTitleFirstPart')}
+ {translate('workspace.intacct.mappingTitle', mappingName)}
+ {translate('workspace.intacct.toggleImportTitleSecondPart')}
+
+ clearSageIntacctErrorField(policyID, mappingName)}
+ >
+ {
+ if (importMapping) {
+ setImportMapping(false);
+ updateSageIntacctMappingValue(policyID, mappingName, CONST.SAGE_INTACCT_MAPPING_VALUE.NONE);
+ } else {
+ setImportMapping(true);
+ updateSageIntacctMappingValue(policyID, mappingName, CONST.SAGE_INTACCT_MAPPING_VALUE.DEFAULT);
+ }
+ }}
+ />
+ {importMapping && (
+
+ Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_MAPPINGS_TYPE.getRoute(policyID, mappingName))}
+ />
+
+ {translationKeys?.descriptionKey ? translate(translationKeys?.descriptionKey) : undefined}
+
+
+ )}
+
+
+ );
+}
+
+SageIntacctToggleMappingsPage.displayName = 'SageIntacctToggleMappingsPage';
+
+export default SageIntacctToggleMappingsPage;
diff --git a/src/pages/workspace/accounting/intacct/import/SageIntacctUserDimensionsPage.tsx b/src/pages/workspace/accounting/intacct/import/SageIntacctUserDimensionsPage.tsx
new file mode 100644
index 000000000000..4a6d5b7bd21c
--- /dev/null
+++ b/src/pages/workspace/accounting/intacct/import/SageIntacctUserDimensionsPage.tsx
@@ -0,0 +1,120 @@
+import React from 'react';
+import {View} from 'react-native';
+import Button from '@components/Button';
+import ConnectionLayout from '@components/ConnectionLayout';
+import FixedFooter from '@components/FixedFooter';
+import Icon from '@components/Icon';
+import * as Illustrations from '@components/Icon/Illustrations';
+import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import TextLink from '@components/TextLink';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import * as Link from '@userActions/Link';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+
+function SageIntacctUserDimensionsPage({policy}: WithPolicyProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+
+ const policyID = policy?.id ?? '-1';
+ const config = policy?.connections?.intacct?.config;
+ const userDimensions = policy?.connections?.intacct?.config?.mappings?.dimensions ?? [];
+
+ return (
+ Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_IMPORT.getRoute(policyID))}
+ >
+ {userDimensions?.length === 0 ? (
+
+
+
+
+
+ {translate('workspace.intacct.addAUserDefinedDimension')}
+
+
+
+
+ {
+ Link.openExternalLink(CONST.SAGE_INTACCT_INSTRUCTIONS);
+ }}
+ >
+ {translate('workspace.intacct.detailedInstructionsLink')}
+
+ {translate('workspace.intacct.detailedInstructionsRestOfSentence')}
+
+
+
+
+ ) : (
+ <>
+
+
+ {
+ Link.openExternalLink(CONST.SAGE_INTACCT_INSTRUCTIONS);
+ }}
+ >
+ {translate('workspace.intacct.detailedInstructionsLink')}
+
+ {translate('workspace.intacct.detailedInstructionsRestOfSentence')}
+
+
+
+ {userDimensions.map((userDimension) => (
+
+ Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EDIT_USER_DIMENSION.getRoute(policyID, userDimension.dimension))}
+ brickRoadIndicator={config?.errorFields?.[`dimension_${userDimension.dimension}`] ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
+ />
+
+ ))}
+
+ >
+ )}
+
+
+
+ );
+}
+
+SageIntacctUserDimensionsPage.displayName = 'SageIntacctUserDimensionsPage';
+
+export default withPolicy(SageIntacctUserDimensionsPage);
diff --git a/src/types/form/SageIntacctDimensionsForm.ts b/src/types/form/SageIntacctDimensionsForm.ts
new file mode 100644
index 000000000000..b2b074801190
--- /dev/null
+++ b/src/types/form/SageIntacctDimensionsForm.ts
@@ -0,0 +1,21 @@
+import type {ValueOf} from 'type-fest';
+import type CONST from '@src/CONST';
+import type Form from './Form';
+
+const INPUT_IDS = {
+ INTEGRATION_NAME: 'integrationName',
+ DIMENSION_TYPE: 'dimensionType',
+} as const;
+
+type InputID = ValueOf;
+
+type SageIntacctDimensionForm = Form<
+ InputID,
+ {
+ [INPUT_IDS.INTEGRATION_NAME]: string;
+ [INPUT_IDS.DIMENSION_TYPE]: typeof CONST.SAGE_INTACCT_MAPPING_VALUE.TAG | typeof CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD;
+ }
+>;
+
+export type {SageIntacctDimensionForm};
+export default INPUT_IDS;
diff --git a/src/types/form/index.ts b/src/types/form/index.ts
index b90a9ad0358f..086c1b052d71 100644
--- a/src/types/form/index.ts
+++ b/src/types/form/index.ts
@@ -53,6 +53,7 @@ export type {PolicyDistanceRateEditForm} from './PolicyDistanceRateEditForm';
export type {WalletAdditionalDetailsForm} from './WalletAdditionalDetailsForm';
export type {NewChatNameForm} from './NewChatNameForm';
export type {WorkForm} from './WorkForm';
+export type {SageIntacctDimensionForm} from './SageIntacctDimensionsForm';
export type {SubscriptionSizeForm} from './SubscriptionSizeForm';
export type {WorkspaceReportFieldForm} from './WorkspaceReportFieldForm';
export type {SageIntactCredentialsForm} from './SageIntactCredentialsForm';
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index af2009b1c7e9..d4f397b6fa53 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -977,14 +977,67 @@ type SageIntacctDataElementWithValue = SageIntacctDataElement & {
* Connection data for Sage Intacct
*/
type SageIntacctConnectionData = {
- /** Collection of bank accounts */
- bankAccounts: SageIntacctDataElement[];
-
/** Collection of credit cards */
creditCards: SageIntacctDataElement[];
+ /** Collection of entities */
+ entities: SageIntacctDataElementWithValue[];
+
+ /** Collection of bank accounts */
+ bankAccounts: SageIntacctDataElement[];
+
/** Collection of vendors */
vendors: SageIntacctDataElementWithValue[];
+
+ /** Collection of journals */
+ journals: SageIntacctDataElementWithValue[];
+
+ /** Collection of items */
+ items: SageIntacctDataElement[];
+
+ /** Collection of tax solutions IDs */
+ taxSolutionIDs: string[];
+};
+
+/** Mapping value for Sage Intacct */
+type SageIntacctMappingValue = ValueOf;
+
+/** Mapping names for Sage Intacct */
+type SageIntacctMappingName = ValueOf;
+
+/**
+ * Sage Intacct dimension type
+ */
+type SageIntacctDimension = {
+ /** Name of user defined dimention */
+ dimension: string;
+
+ /** Mapping value for user defined dimention */
+ mapping: typeof CONST.SAGE_INTACCT_MAPPING_VALUE.TAG | typeof CONST.SAGE_INTACCT_MAPPING_VALUE.REPORT_FIELD;
+};
+
+/** Mapping type for Sage Intacct */
+type SageIntacctMappingType = {
+ /** Whether should sync items for Sage Intacct */
+ syncItems: boolean;
+
+ /** Mapping type for Sage Intacct */
+ departments: SageIntacctMappingValue;
+
+ /** Mapping type for Sage Intacct */
+ classes: SageIntacctMappingValue;
+
+ /** Mapping type for Sage Intacct */
+ locations: SageIntacctMappingValue;
+
+ /** Mapping type for Sage Intacct */
+ customers: SageIntacctMappingValue;
+
+ /** Mapping type for Sage Intacct */
+ projects: SageIntacctMappingValue;
+
+ /** User defined dimention type for Sage Intacct */
+ dimensions: SageIntacctDimension[];
};
/** Configuration of automatic synchronization from Sage Intacct to the app */
@@ -1002,6 +1055,11 @@ type SageIntacctSyncConfig = {
syncReimbursedReports: boolean | string;
};
+/**
+ * Connection config for Sage Intacct
+ */
+type SageIntacctOfflineStateKeys = keyof SageIntacctMappingType | `dimension_${string}`;
+
/**
* Connection config for Sage Intacct
*/
@@ -1019,6 +1077,18 @@ type SageIntacctConnectionsConfig = OnyxCommon.OnyxValueWithOfflineFeedback<
userID: string;
};
+ /** Sage Intacct mappings */
+ mappings: SageIntacctMappingType;
+
+ /** Sage Intacct tax */
+ tax: {
+ /** Sage Intacct tax solution ID */
+ taxSolutionID: string;
+
+ /** Whether should sync tax with Sage Intacct */
+ syncTax: boolean;
+ };
+
/** Sage Intacct export configs */
export: OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Export date type */
@@ -1067,7 +1137,7 @@ type SageIntacctConnectionsConfig = OnyxCommon.OnyxValueWithOfflineFeedback<
/** Collection of form field errors */
errorFields?: OnyxCommon.ErrorFields;
},
- keyof SageIntacctSyncConfig | keyof SageIntacctAutoSyncConfig
+ SageIntacctOfflineStateKeys | keyof SageIntacctSyncConfig | keyof SageIntacctAutoSyncConfig
>;
/** State of integration connection */
@@ -1446,6 +1516,7 @@ export type {
PolicyConnectionSyncStage,
PolicyConnectionSyncProgress,
Connections,
+ SageIntacctOfflineStateKeys,
ConnectionName,
Tenant,
Account,
@@ -1459,6 +1530,10 @@ export type {
NetSuiteAccount,
NetSuiteCustomFormIDOptions,
NetSuiteCustomFormID,
+ SageIntacctMappingValue,
+ SageIntacctMappingType,
+ SageIntacctMappingName,
+ SageIntacctDimension,
SageIntacctDataElementWithValue,
SageIntacctDataElement,
SageIntacctConnectionsConfig,