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(true)} + /> + + + { + 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} + /> + + ))} + + + )} + +