diff --git a/src/CONST.ts b/src/CONST.ts index 7d0580e63827..b90dbd18f51e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1372,6 +1372,12 @@ const CONST = { PROVINCIAL_TAX_POSTING_ACCOUNT: 'provincialTaxPostingAccount', ALLOW_FOREIGN_CURRENCY: 'allowForeignCurrency', EXPORT_TO_NEXT_OPEN_PERIOD: 'exportToNextOpenPeriod', + AUTO_SYNC: 'autoSync', + REIMBURSEMENT_ACCOUNT_ID: 'reimbursementAccountID', + COLLECTION_ACCOUNT: 'collectionAccount', + AUTO_CREATE_ENTITIES: 'autoCreateEntities', + APPROVAL_ACCOUNT: 'approvalAccount', + CUSTOM_FORM_ID_OPTIONS: 'customFormIDOptions', TOKEN_INPUT_STEP_NAMES: ['1', '2,', '3', '4', '5'], TOKEN_INPUT_STEP_KEYS: { 0: 'installBundle', @@ -1383,6 +1389,12 @@ const CONST = { IMPORT_FIELDS: ['departments', 'classes', 'locations', 'customers', 'jobs'], IMPORT_CUSTOM_FIELDS: ['customSegments', 'customLists'], SYNC_OPTIONS: { + SYNC_REIMBURSED_REPORTS: 'syncReimbursedReports', + SYNC_PEOPLE: 'syncPeople', + ENABLE_NEW_CATEGORIES: 'enableNewCategories', + EXPORT_REPORTS_TO: 'exportReportsTo', + EXPORT_VENDOR_BILLS_TO: 'exportVendorBillsTo', + EXPORT_JOURNALS_TO: 'exportJournalsTo', SYNC_TAX: 'syncTax', }, }, @@ -1399,6 +1411,12 @@ const CONST = { JOURNAL_ENTRY: 'JOURNAL_ENTRY', }, + NETSUITE_MAP_EXPORT_DESTINATION: { + EXPENSE_REPORT: 'expenseReport', + VENDOR_BILL: 'vendorBill', + JOURNAL_ENTRY: 'journalEntry', + }, + NETSUITE_INVOICE_ITEM_PREFERENCE: { CREATE: 'create', SELECT: 'select', @@ -1414,6 +1432,27 @@ const CONST = { NON_REIMBURSABLE: 'nonreimbursable', }, + NETSUITE_REPORTS_APPROVAL_LEVEL: { + REPORTS_APPROVED_NONE: 'REPORTS_APPROVED_NONE', + REPORTS_SUPERVISOR_APPROVED: 'REPORTS_SUPERVISOR_APPROVED', + REPORTS_ACCOUNTING_APPROVED: 'REPORTS_ACCOUNTING_APPROVED', + REPORTS_APPROVED_BOTH: 'REPORTS_APPROVED_BOTH', + }, + + NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL: { + VENDOR_BILLS_APPROVED_NONE: 'VENDOR_BILLS_APPROVED_NONE', + VENDOR_BILLS_APPROVAL_PENDING: 'VENDOR_BILLS_APPROVAL_PENDING', + VENDOR_BILLS_APPROVED: 'VENDOR_BILLS_APPROVED', + }, + + NETSUITE_JOURNALS_APPROVAL_LEVEL: { + JOURNALS_APPROVED_NONE: 'JOURNALS_APPROVED_NONE', + JOURNALS_APPROVAL_PENDING: 'JOURNALS_APPROVAL_PENDING', + JOURNALS_APPROVED: 'JOURNALS_APPROVED', + }, + + NETSUITE_APPROVAL_ACCOUNT_DEFAULT: 'APPROVAL_ACCOUNT_DEFAULT', + /** * Countries where tax setting is permitted (Strings are in the format of Netsuite's Country type/enum) * diff --git a/src/ROUTES.ts b/src/ROUTES.ts index e9a88d3d2bb9..81d55c63e801 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1025,6 +1025,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/connections/netsuite/export/provincial-tax-posting-account/select', getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/export/provincial-tax-posting-account/select` as const, }, + POLICY_ACCOUNTING_NETSUITE_ADVANCED: { + route: 'settings/workspaces/:policyID/connections/netsuite/advanced/', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/connections/netsuite/advanced/` as const, + }, POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES: { route: 'settings/workspaces/:policyID/accounting/sage-intacct/prerequisites', getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/sage-intacct/prerequisites` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index aa881cd3d6fc..82c93909e2cb 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -288,6 +288,7 @@ const SCREENS = { NETSUITE_INVOICE_ITEM_SELECT: 'Policy_Accounting_NetSuite_Invoice_Item_Select', NETSUITE_TAX_POSTING_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Tax_Posting_Account_Select', NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT: 'Policy_Accounting_NetSuite_Provincial_Tax_Posting_Account_Select', + NETSUITE_ADVANCED: 'Policy_Accounting_NetSuite_Advanced', 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', diff --git a/src/languages/en.ts b/src/languages/en.ts index e23cc8c4dd50..153d2e40fc25 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2100,14 +2100,11 @@ export default { outOfPocketTaxEnabledError: 'Journal entries are unavailable when taxes are enabled. Please choose a different export option.', outOfPocketLocationEnabledError: 'Vendor bills are unavailable when locations are enabled. Please choose a different export option.', advancedConfig: { - advanced: 'Advanced', - autoSync: 'Auto-sync', autoSyncDescription: 'Expensify will automatically sync with QuickBooks Online every day.', inviteEmployees: 'Invite employees', inviteEmployeesDescription: 'Import Quickbooks Online employee records and invite employees to this workspace.', createEntities: 'Auto-create entities', createEntitiesDescription: "Expensify will automatically create vendors in QuickBooks Online if they don't exist already, and auto-create customers when exporting invoices.", - reimbursedReports: 'Sync reimbursed reports', reimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the Quickbooks Online account below.', qboBillPaymentAccount: 'QuickBooks bill payment account', qboInvoiceCollectionAccount: 'QuickBooks invoice collections account', @@ -2172,11 +2169,8 @@ export default { salesInvoice: 'Sales invoice', exportInvoicesDescription: 'Sales invoices always display the date on which the invoice was sent.', advancedConfig: { - advanced: 'Advanced', - autoSync: 'Auto-sync', autoSyncDescription: 'Expensify will automatically sync with Xero every day.', purchaseBillStatusTitle: 'Purchase bill status', - reimbursedReports: 'Sync reimbursed reports', reimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the Xero account below.', xeroBillPaymentAccount: 'Xero bill payment account', xeroInvoiceCollectionAccount: 'Xero invoice collections account', @@ -2295,6 +2289,49 @@ export default { }, }, }, + advancedConfig: { + autoSyncDescription: 'Expensify will automatically sync with NetSuite every day.', + reimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the NetSuite account below.', + reimbursementsAccount: 'Reimbursements account', + collectionsAccount: 'Collections account', + approvalAccount: 'A/P approval account', + defaultApprovalAccount: 'NetSuite default', + inviteEmployees: 'Invite employees and set approvals', + inviteEmployeesDescription: + 'Import NetSuite employee records and invite employees to this workspace. Your approval workflow will default to manager approval and can be further configured on the *Members* page.', + autoCreateEntities: 'Auto-create employees/vendors', + enableCategories: 'Enable newly imported categories', + customFormID: 'Custom form ID', + customFormIDDescription: + 'By default, Expensify will create entries using the preferred transaction form set in NetSuite. Alternatively, you have the option to designate a specific transaction form to be used.', + customFormIDReimbursable: 'Reimbursable expense', + customFormIDNonReimbursable: 'Non-reimbursable expense', + exportReportsTo: { + label: 'Expense report approval level', + values: { + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_NONE]: 'NetSuite default preference', + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_SUPERVISOR_APPROVED]: 'Only supervisor approved', + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_ACCOUNTING_APPROVED]: 'Only accounting approved', + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_BOTH]: 'Supervisor and accounting approved', + }, + }, + exportVendorBillsTo: { + label: 'Vendor bill approval level', + values: { + [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED_NONE]: 'NetSuite default preference', + [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVAL_PENDING]: 'Pending approval', + [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED]: 'Approved for posting', + }, + }, + exportJournalsTo: { + label: 'Journal entry approval level', + values: { + [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED_NONE]: 'NetSuite default preference', + [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVAL_PENDING]: 'Pending approval', + [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED]: 'Approved for posting', + }, + }, + }, noAccountsFound: 'No accounts found', noAccountsFoundDescription: 'Add the account in NetSuite and sync the connection again.', noVendorsFound: 'No vendors found', @@ -2847,6 +2884,8 @@ export default { exportPreferredExporterSubNote: 'Once set, the preferred exporter will see reports for export in their account.', exportAs: 'Export as', defaultVendor: 'Default vendor', + autoSync: 'Auto-sync', + reimbursedReports: 'Sync reimbursed reports', }, bills: { manageYourBills: 'Manage your bills', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5b4e25b8a981..6091cfa8559d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2134,14 +2134,11 @@ export default { 'QuickBooks Online no permite lugares en facturas de proveedores o cheques. Como tienes activadas los lugares en tu espacio de trabajo, estas opciones de exportación no están disponibles.', advancedConfig: { - advanced: 'Avanzado', - autoSync: 'Autosincronización', autoSyncDescription: 'Expensify se sincronizará automáticamente con QuickBooks Online todos los días.', inviteEmployees: 'Invitar empleados', inviteEmployeesDescription: 'Importe los registros de los empleados de Quickbooks Online e invítelos a este espacio de trabajo.', createEntities: 'Crear entidades automáticamente', createEntitiesDescription: 'Expensify creará automáticamente proveedores en QuickBooks Online si aún no existen, y creará automáticamente clientes al exportar facturas.', - reimbursedReports: 'Sincronizar informes reembolsados', reimbursedReportsDescription: 'Cada vez que se pague un informe utilizando Expensify ACH, se creará el correspondiente pago de la factura en la cuenta de Quickbooks Online indicadas a continuación.', qboBillPaymentAccount: 'Cuenta de pago de las facturas de QuickBooks', @@ -2212,11 +2209,8 @@ export default { salesInvoice: 'Factura de venta', exportInvoicesDescription: 'Las facturas de venta siempre muestran la fecha en la que se envió la factura.', advancedConfig: { - advanced: 'Avanzado', - autoSync: 'Autosincronización', autoSyncDescription: 'Expensify se sincronizará automáticamente con Xero todos los días.', purchaseBillStatusTitle: 'Estado de la factura de compra', - reimbursedReports: 'Sincronizar informes reembolsados', reimbursedReportsDescription: 'Cada vez que se pague un informe utilizando Expensify ACH, se creará el correspondiente pago de la factura en la cuenta de Xero indicadas a continuación.', xeroBillPaymentAccount: 'Cuenta de pago de las facturas de Xero', @@ -2336,6 +2330,50 @@ export default { }, }, }, + advancedConfig: { + autoSyncDescription: 'Expensify se sincronizará automáticamente con NetSuite todos los días.', + reimbursedReportsDescription: + 'Cada vez que se pague un informe utilizando Expensify ACH, se creará el correspondiente pago de la factura en la cuenta de NetSuite indicadas a continuación.', + reimbursementsAccount: 'Cuenta de reembolsos', + collectionsAccount: 'Cuenta de cobros', + approvalAccount: 'Cuenta de aprobación de cuentas por pagar', + defaultApprovalAccount: 'Preferencia predeterminada de NetSuite', + inviteEmployees: 'Invitar empleados y establecer aprobaciones', + inviteEmployeesDescription: + 'Importar registros de empleados de NetSuite e invitar a empleados a este espacio de trabajo. Su flujo de trabajo de aprobación será por defecto la aprobación del gerente y se puede configurar más en la página *Miembros*.', + autoCreateEntities: 'Crear automáticamente empleados/proveedores', + enableCategories: 'Activar categorías recién importadas', + customFormID: 'ID de formulario personalizado', + customFormIDDescription: + 'Por defecto, Expensify creará entradas utilizando el formulario de transacción preferido configurado en NetSuite. Alternativamente, tienes la opción de designar un formulario de transacción específico para ser utilizado.', + customFormIDReimbursable: 'Gasto reembolsable', + customFormIDNonReimbursable: 'Gasto no reembolsable', + exportReportsTo: { + label: 'Nivel de aprobación del informe de gastos', + values: { + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_SUPERVISOR_APPROVED]: 'Solo aprobado por el supervisor', + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_ACCOUNTING_APPROVED]: 'Solo aprobado por contabilidad', + [CONST.NETSUITE_REPORTS_APPROVAL_LEVEL.REPORTS_APPROVED_BOTH]: 'Aprobado por supervisor y contabilidad', + }, + }, + exportVendorBillsTo: { + label: 'Nivel de aprobación de facturas de proveedores', + values: { + [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', + [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVAL_PENDING]: 'Aprobación pendiente', + [CONST.NETSUITE_VENDOR_BILLS_APPROVAL_LEVEL.VENDOR_BILLS_APPROVED]: 'Aprobado para publicación', + }, + }, + exportJournalsTo: { + label: 'Nivel de aprobación de asientos contables', + values: { + [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED_NONE]: 'Preferencia predeterminada de NetSuite', + [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVAL_PENDING]: 'Aprobación pendiente', + [CONST.NETSUITE_JOURNALS_APPROVAL_LEVEL.JOURNALS_APPROVED]: 'Aprobado para publicación', + }, + }, + }, noAccountsFound: 'No se han encontrado cuentas', noAccountsFoundDescription: 'Añade la cuenta en NetSuite y sincroniza la conexión de nuevo.', noVendorsFound: 'No se han encontrado proveedores', @@ -2828,6 +2866,8 @@ export default { exportPreferredExporterSubNote: 'Una vez configurado, el exportador preferido verá los informes para exportar en tu cuenta.', exportAs: 'Exportar cómo', defaultVendor: 'Proveedor predeterminado', + autoSync: 'Autosincronización', + reimbursedReports: 'Sincronizar informes reembolsados', }, card: { header: 'Desbloquea Tarjetas Expensify gratis', diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 5ca367216871..e2ea98bb3901 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -251,6 +251,12 @@ const WRITE_COMMANDS = { UPDATE_NETSUITE_TAX_POSTING_ACCOUNT: 'UpdateNetSuiteTaxPostingAccount', UPDATE_NETSUITE_ALLOW_FOREIGN_CURRENCY: 'UpdateNetSuiteAllowForeignCurrency', UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD: 'UpdateNetSuiteExportToNextOpenPeriod', + UPDATE_NETSUITE_AUTO_SYNC: 'UpdateNetSuiteAutoSync', + UPDATE_NETSUITE_SYNC_REIMBURSED_REPORTS: 'UpdateNetSuiteSyncReimbursedReports', + UPDATE_NETSUITE_SYNC_PEOPLE: 'UpdateNetSuiteSyncPeople', + UPDATE_NETSUITE_AUTO_CREATE_ENTITIES: 'UpdateNetSuiteAutoCreateEntities', + UPDATE_NETSUITE_ENABLE_NEW_CATEGORIES: 'UpdateNetSuiteEnableNewCategories', + UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_ENABLED: 'UpdateNetSuiteCustomFormIDOptionsEnabled', REQUEST_EXPENSIFY_CARD_LIMIT_INCREASE: 'RequestExpensifyCardLimitIncrease', CONNECT_POLICY_TO_SAGE_INTACCT: 'ConnectPolicyToSageIntacct', CONNECT_POLICY_TO_NETSUITE: 'ConnectPolicyToNetSuite', @@ -517,6 +523,12 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_NETSUITE_TAX_POSTING_ACCOUNT]: Parameters.UpdateNetSuiteGenericTypeParams<'bankAccountID', string>; [WRITE_COMMANDS.UPDATE_NETSUITE_ALLOW_FOREIGN_CURRENCY]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; [WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_AUTO_SYNC]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_REIMBURSED_REPORTS]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_PEOPLE]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_AUTO_CREATE_ENTITIES]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_ENABLE_NEW_CATEGORIES]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; + [WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_ENABLED]: Parameters.UpdateNetSuiteGenericTypeParams<'enabled', boolean>; }; const READ_COMMANDS = { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index da577d17fe07..ab91bccc8194 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -346,6 +346,7 @@ const SettingsModalStackNavigator = createModalStackNavigator('../../../../pages/workspace/accounting/netsuite/export/NetSuiteTaxPostingAccountSelectPage').default, [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT]: () => require('../../../../pages/workspace/accounting/netsuite/export/NetSuiteProvincialTaxPostingAccountSelectPage').default, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED]: () => require('../../../../pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage').default, [SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES]: () => require('../../../../pages/workspace/accounting/intacct/IntacctPrerequisitesPage').default, [SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS]: () => 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 5c9275cfab17..a2dba2b38938 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -71,6 +71,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_INVOICE_ITEM_SELECT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TAX_POSTING_ACCOUNT_SELECT, SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT, + SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED, SCREENS.WORKSPACE.ACCOUNTING.SAGE_INTACCT_PREREQUISITES, SCREENS.WORKSPACE.ACCOUNTING.ENTER_SAGE_INTACCT_CREDENTIALS, SCREENS.WORKSPACE.ACCOUNTING.EXISTING_SAGE_INTACCT_CONNECTIONS, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 65fd44139456..30918d81d22b 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -395,6 +395,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT]: { path: ROUTES.POLICY_ACCOUNTING_NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT.route, }, + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED]: { + path: ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.route, + }, [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}, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index f838ac7a8603..f4521afb8766 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -466,6 +466,9 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_PROVINCIAL_TAX_POSTING_ACCOUNT_SELECT]: { policyID: string; }; + [SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_ADVANCED]: { + policyID: string; + }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index d075f8653d79..ca61fceaaa78 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -533,6 +533,10 @@ function clearNetSuiteErrorField(policyID: string, fieldName: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {netsuite: {options: {config: {errorFields: {[fieldName]: null}}}}}}); } +function clearNetSuiteAutoSyncErrorField(policyID: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {connections: {netsuite: {config: {errorFields: {autoSync: null}}}}}); +} + function setWorkspaceReimbursement(policyID: string, reimbursementChoice: ValueOf, reimburserEmail: string) { const policy = getPolicy(policyID); @@ -3056,6 +3060,7 @@ export { clearQBOErrorField, clearXeroErrorField, clearNetSuiteErrorField, + clearNetSuiteAutoSyncErrorField, clearWorkspaceReimbursementErrors, setWorkspaceCurrencyDefault, setForeignCurrencyDefault, diff --git a/src/libs/actions/connections/NetSuiteCommands.ts b/src/libs/actions/connections/NetSuiteCommands.ts index 1664e0ff4fbd..5d04613561ad 100644 --- a/src/libs/actions/connections/NetSuiteCommands.ts +++ b/src/libs/actions/connections/NetSuiteCommands.ts @@ -114,6 +114,92 @@ function updateNetSuiteOnyxData( + policyID: string, + settingName: TSettingName, + settingValue: Partial, + oldSettingValue: Partial, +) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + netsuite: { + options: { + config: { + syncOptions: { + [settingName]: settingValue ?? null, + 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: { + netsuite: { + options: { + config: { + syncOptions: { + [settingName]: oldSettingValue ?? null, + pendingFields: { + [settingName]: null, + }, + }, + errorFields: { + [settingName]: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + }, + }, + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + netsuite: { + options: { + config: { + syncOptions: { + [settingName]: settingValue ?? null, + pendingFields: { + [settingName]: null, + }, + }, + errorFields: { + [settingName]: null, + }, + }, + }, + }, + }, + }, + }, + ]; + return {optimisticData, failureData, successData}; +} + function updateNetSuiteSubsidiary(policyID: string, newSubsidiary: SubsidiaryParam, oldSubsidiary: SubsidiaryParam) { const onyxData: OnyxData = { optimisticData: [ @@ -198,85 +284,7 @@ function updateNetSuiteSubsidiary(policyID: string, newSubsidiary: SubsidiaryPar } function updateNetSuiteSyncTaxConfiguration(policyID: string, isSyncTaxEnabled: boolean) { - const onyxData: OnyxData = { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - connections: { - netsuite: { - options: { - config: { - syncOptions: { - syncTax: isSyncTaxEnabled, - }, - // TODO: Fixing in the PR for Import Mapping https://github.com/Expensify/App/pull/44743 - // pendingFields: { - // syncTax: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, - // }, - errorFields: { - syncTax: null, - }, - }, - }, - }, - }, - }, - }, - ], - successData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - connections: { - netsuite: { - options: { - config: { - syncOptions: { - syncTax: isSyncTaxEnabled, - }, - // TODO: Fixing in the PR for Import Mapping https://github.com/Expensify/App/pull/44743 - // pendingFields: { - // syncTax: null - // }, - errorFields: { - syncTax: null, - }, - }, - }, - }, - }, - }, - }, - ], - failureData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - connections: { - netsuite: { - options: { - config: { - syncOptions: { - syncTax: !isSyncTaxEnabled, - }, - // pendingFields: { - // syncTax: null, - // }, - errorFields: { - syncTax: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), - }, - }, - }, - }, - }, - }, - }, - ], - }; + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_TAX, isSyncTaxEnabled, !isSyncTaxEnabled); const params = { policyID, @@ -451,6 +459,142 @@ function updateNetSuiteExportToNextOpenPeriod(policyID: string, value: boolean, API.write(WRITE_COMMANDS.UPDATE_NETSUITE_EXPORT_TO_NEXT_OPEN_PERIOD, parameters, onyxData); } +function updateNetSuiteAutoSync(policyID: string, value: boolean) { + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + netsuite: { + config: { + autoSync: { + enabled: value, + }, + pendingFields: { + autoSync: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + errorFields: { + autoSync: null, + }, + }, + }, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + netsuite: { + config: { + autoSync: { + enabled: !value, + }, + pendingFields: { + autoSync: null, + }, + errorFields: { + autoSync: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }, + }, + }, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + netsuite: { + config: { + autoSync: { + enabled: value, + }, + pendingFields: { + autoSync: null, + }, + errorFields: { + autoSync: null, + }, + }, + }, + }, + }, + }, + ]; + + const parameters = { + policyID, + enabled: value, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_AUTO_SYNC, parameters, {optimisticData, failureData, successData}); +} + +function updateNetSuiteSyncReimbursedReports(policyID: string, value: boolean) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_REIMBURSED_REPORTS, value, !value); + + const parameters = { + policyID, + enabled: value, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_REIMBURSED_REPORTS, parameters, onyxData); +} + +function updateNetSuiteSyncPeople(policyID: string, value: boolean) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_PEOPLE, value, !value); + + const parameters = { + policyID, + enabled: value, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_SYNC_PEOPLE, parameters, onyxData); +} + +function updateNetSuiteAutoCreateEntities(policyID: string, value: boolean) { + const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.AUTO_CREATE_ENTITIES, value, !value); + + const parameters = { + policyID, + enabled: value, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_AUTO_CREATE_ENTITIES, parameters, onyxData); +} + +function updateNetSuiteEnableNewCategories(policyID: string, value: boolean) { + const onyxData = updateNetSuiteSyncOptionsOnyxData(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.ENABLE_NEW_CATEGORIES, value, !value); + + const parameters = { + policyID, + enabled: value, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_ENABLE_NEW_CATEGORIES, parameters, onyxData); +} + +function updateNetSuiteCustomFormIDOptionsEnabled(policyID: string, value: boolean) { + const data = { + enabled: value, + }; + const oldData = { + enabled: !value, + }; + const onyxData = updateNetSuiteOnyxData(policyID, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS, data, oldData); + + const parameters = { + policyID, + enabled: value, + }; + API.write(WRITE_COMMANDS.UPDATE_NETSUITE_CUSTOM_FORM_ID_OPTIONS_ENABLED, parameters, onyxData); +} + export { updateNetSuiteSubsidiary, updateNetSuiteSyncTaxConfiguration, @@ -469,5 +613,11 @@ export { updateNetSuiteProvincialTaxPostingAccount, updateNetSuiteAllowForeignCurrency, updateNetSuiteExportToNextOpenPeriod, + updateNetSuiteAutoSync, + updateNetSuiteSyncReimbursedReports, + updateNetSuiteSyncPeople, + updateNetSuiteAutoCreateEntities, + updateNetSuiteEnableNewCategories, + updateNetSuiteCustomFormIDOptionsEnabled, connectPolicyToNetSuite, }; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 968c42828f1f..ca4c990aea2a 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -115,7 +115,7 @@ function accountingIntegrationData( ), onImportPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT.getRoute(policyID)), onExportPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_EXPORT.getRoute(policyID)), - onAdvancedPagePress: () => {}, + onAdvancedPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)), }; case CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT: return { diff --git a/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx new file mode 100644 index 000000000000..741395555f4a --- /dev/null +++ b/src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx @@ -0,0 +1,286 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +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 * as Connections from '@libs/actions/connections/NetSuiteCommands'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import type {DividerLineItem, MenuItem, ToggleItem} from '@pages/workspace/accounting/netsuite/types'; +import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; +import withPolicyConnections from '@pages/workspace/withPolicyConnections'; +import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; +import * as Policy from '@userActions/Policy/Policy'; +import CONST from '@src/CONST'; + +function NetSuiteAdvancedPage({policy}: WithPolicyConnectionsProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const policyID = policy?.id ?? '-1'; + + const config = policy?.connections?.netsuite?.options.config; + const autoSyncConfig = policy?.connections?.netsuite?.config; + + const {payableList} = policy?.connections?.netsuite?.options?.data ?? {}; + + const selectedReimbursementAccount = useMemo( + () => (payableList ?? []).find((payableAccount) => payableAccount.id === config?.reimbursementAccountID), + [payableList, config?.reimbursementAccountID], + ); + const selectedCollectionAccount = useMemo(() => (payableList ?? []).find((payableAccount) => payableAccount.id === config?.collectionAccount), [payableList, config?.collectionAccount]); + const selectedApprovalAccount = useMemo(() => { + if (config?.approvalAccount === CONST.NETSUITE_APPROVAL_ACCOUNT_DEFAULT) { + return { + id: CONST.NETSUITE_APPROVAL_ACCOUNT_DEFAULT, + name: translate('workspace.netsuite.advancedConfig.defaultApprovalAccount'), + }; + } + return (payableList ?? []).find((payableAccount) => payableAccount.id === config?.approvalAccount); + }, [config?.approvalAccount, payableList, translate]); + + const menuItems: Array = [ + { + type: 'toggle', + title: translate('workspace.accounting.autoSync'), + subtitle: translate('workspace.netsuite.advancedConfig.autoSyncDescription'), + isActive: !!autoSyncConfig?.autoSync.enabled, + switchAccessibilityLabel: translate('workspace.netsuite.advancedConfig.autoSyncDescription'), + shouldPlaceSubtitleBelowSwitch: true, + onCloseError: () => Policy.clearNetSuiteAutoSyncErrorField(policyID), + onToggle: (isEnabled) => Connections.updateNetSuiteAutoSync(policyID, isEnabled), + pendingAction: autoSyncConfig?.pendingFields?.autoSync, + errors: ErrorUtils.getLatestErrorField(autoSyncConfig, CONST.NETSUITE_CONFIG.AUTO_SYNC), + }, + { + type: 'divider', + key: 'divider1', + }, + { + type: 'toggle', + title: translate('workspace.accounting.reimbursedReports'), + subtitle: translate('workspace.netsuite.advancedConfig.reimbursedReportsDescription'), + isActive: !!config?.syncOptions.syncReimbursedReports, + switchAccessibilityLabel: translate('workspace.netsuite.advancedConfig.reimbursedReportsDescription'), + shouldPlaceSubtitleBelowSwitch: true, + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_REIMBURSED_REPORTS), + onToggle: (isEnabled) => Connections.updateNetSuiteSyncReimbursedReports(policyID, isEnabled), + pendingAction: config?.syncOptions.pendingFields?.syncReimbursedReports, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_REIMBURSED_REPORTS), + shouldHide: config?.reimbursableExpensesExportDestination === CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY, + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.reimbursementsAccount'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.reimbursementAccountID ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: selectedReimbursementAccount ? selectedReimbursementAccount.name : undefined, + pendingAction: config?.pendingFields?.reimbursementAccountID, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.REIMBURSEMENT_ACCOUNT_ID), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.REIMBURSEMENT_ACCOUNT_ID), + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.collectionsAccount'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.collectionAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: selectedCollectionAccount ? selectedCollectionAccount.name : undefined, + pendingAction: config?.pendingFields?.collectionAccount, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.COLLECTION_ACCOUNT), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.COLLECTION_ACCOUNT), + }, + { + type: 'divider', + key: 'divider2', + }, + { + type: 'toggle', + title: translate('workspace.netsuite.advancedConfig.inviteEmployees'), + subtitle: translate('workspace.netsuite.advancedConfig.inviteEmployeesDescription'), + isActive: !!config?.syncOptions.syncPeople, + switchAccessibilityLabel: translate('workspace.netsuite.advancedConfig.inviteEmployeesDescription'), + shouldPlaceSubtitleBelowSwitch: true, + shouldParseSubtitle: true, + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_PEOPLE), + onToggle: (isEnabled) => Connections.updateNetSuiteSyncPeople(policyID, isEnabled), + pendingAction: config?.syncOptions.pendingFields?.syncPeople, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_PEOPLE), + }, + { + type: 'toggle', + title: translate('workspace.netsuite.advancedConfig.autoCreateEntities'), + isActive: !!config?.autoCreateEntities, + switchAccessibilityLabel: translate('workspace.netsuite.advancedConfig.autoCreateEntities'), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.AUTO_CREATE_ENTITIES), + onToggle: (isEnabled) => Connections.updateNetSuiteAutoCreateEntities(policyID, isEnabled), + pendingAction: config?.pendingFields?.autoCreateEntities, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.AUTO_CREATE_ENTITIES), + }, + { + type: 'divider', + key: 'divider3', + }, + { + type: 'toggle', + title: translate('workspace.netsuite.advancedConfig.enableCategories'), + isActive: !!config?.syncOptions.enableNewCategories, + switchAccessibilityLabel: translate('workspace.netsuite.advancedConfig.enableCategories'), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.ENABLE_NEW_CATEGORIES), + onToggle: (isEnabled) => Connections.updateNetSuiteEnableNewCategories(policyID, isEnabled), + pendingAction: config?.syncOptions.pendingFields?.enableNewCategories, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.ENABLE_NEW_CATEGORIES), + }, + { + type: 'divider', + key: 'divider4', + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.exportReportsTo.label'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.exportReportsTo ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: config?.syncOptions.exportReportsTo ? translate(`workspace.netsuite.advancedConfig.exportReportsTo.values.${config.syncOptions.exportReportsTo}`) : undefined, + pendingAction: config?.syncOptions.pendingFields?.exportReportsTo, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_REPORTS_TO), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_REPORTS_TO), + shouldHide: config?.reimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.EXPENSE_REPORT, + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.exportVendorBillsTo.label'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.exportVendorBillsTo ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: config?.syncOptions.exportVendorBillsTo ? translate(`workspace.netsuite.advancedConfig.exportVendorBillsTo.values.${config.syncOptions.exportVendorBillsTo}`) : undefined, + pendingAction: config?.syncOptions.pendingFields?.exportVendorBillsTo, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_VENDOR_BILLS_TO), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_VENDOR_BILLS_TO), + shouldHide: + config?.reimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.VENDOR_BILL && + config?.nonreimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.VENDOR_BILL, + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.exportJournalsTo.label'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.exportJournalsTo ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: config?.syncOptions.exportJournalsTo ? translate(`workspace.netsuite.advancedConfig.exportJournalsTo.values.${config.syncOptions.exportJournalsTo}`) : undefined, + pendingAction: config?.syncOptions.pendingFields?.exportJournalsTo, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_JOURNALS_TO), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.EXPORT_JOURNALS_TO), + shouldHide: + config?.reimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY && + config?.nonreimbursableExpensesExportDestination !== CONST.NETSUITE_EXPORT_DESTINATION.JOURNAL_ENTRY, + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.approvalAccount'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.approvalAccount ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: selectedApprovalAccount ? selectedApprovalAccount.name : undefined, + pendingAction: config?.pendingFields?.approvalAccount, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.APPROVAL_ACCOUNT), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.APPROVAL_ACCOUNT), + }, + { + type: 'divider', + key: 'divider5', + }, + { + type: 'toggle', + title: translate('workspace.netsuite.advancedConfig.customFormID'), + subtitle: translate('workspace.netsuite.advancedConfig.customFormIDDescription'), + isActive: !!config?.customFormIDOptions?.enabled, + switchAccessibilityLabel: translate('workspace.netsuite.advancedConfig.customFormIDDescription'), + shouldPlaceSubtitleBelowSwitch: true, + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), + onToggle: (isEnabled) => Connections.updateNetSuiteCustomFormIDOptionsEnabled(policyID, isEnabled), + pendingAction: config?.pendingFields?.customFormIDOptions, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.customFormIDReimbursable'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.customFormIDOptions ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: config?.customFormIDOptions?.reimbursable[CONST.NETSUITE_MAP_EXPORT_DESTINATION[config.reimbursableExpensesExportDestination]], + pendingAction: config?.pendingFields?.customFormIDOptions, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), + shouldHide: !config?.customFormIDOptions?.enabled, + }, + { + type: 'menuitem', + description: translate('workspace.netsuite.advancedConfig.customFormIDNonReimbursable'), + onPress: () => {}, // TODO: This will be implemented in a future PR + brickRoadIndicator: config?.errorFields?.customFormIDOptions ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + title: config?.customFormIDOptions?.reimbursable[CONST.NETSUITE_MAP_EXPORT_DESTINATION[config.nonreimbursableExpensesExportDestination]], + pendingAction: config?.pendingFields?.customFormIDOptions, + errors: ErrorUtils.getLatestErrorField(config, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), + onCloseError: () => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.CUSTOM_FORM_ID_OPTIONS), + shouldHide: !config?.customFormIDOptions?.enabled, + }, + ]; + + return ( + + {menuItems + .filter((item) => !item.shouldHide) + .map((item) => { + switch (item.type) { + case 'divider': + return ( + + ); + case 'toggle': + // eslint-disable-next-line no-case-declarations + const {type, shouldHide, ...rest} = item; + return ( + + ); + default: + return ( + + + + ); + } + })} + + ); +} + +NetSuiteAdvancedPage.displayName = 'NetSuiteAdvancedPage'; + +export default withPolicyConnections(NetSuiteAdvancedPage); diff --git a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx index 8c9e4bbf947f..6ff9f7bdb60f 100644 --- a/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx +++ b/src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx @@ -81,6 +81,7 @@ function NetSuiteImportPage({policy}: WithPolicyConnectionsProps) { onToggle={(isEnabled: boolean) => { updateNetSuiteSyncTaxConfiguration(policyID, isEnabled); }} + pendingAction={config?.syncOptions.pendingFields?.syncTax} errors={ErrorUtils.getLatestErrorField(config ?? {}, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_TAX)} onCloseError={() => Policy.clearNetSuiteErrorField(policyID, CONST.NETSUITE_CONFIG.SYNC_OPTIONS.SYNC_TAX)} /> diff --git a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx index cfe10e29ab8d..f8ded59659eb 100644 --- a/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx +++ b/src/pages/workspace/accounting/qbo/advanced/QuickbooksAdvancedPage.tsx @@ -70,7 +70,7 @@ function QuickbooksAdvancedPage({policy}: WithPolicyConnectionsProps) { const qboToggleSettingItems: ToggleSettingOptionRowProps[] = [ { - title: translate('workspace.qbo.advancedConfig.autoSync'), + title: translate('workspace.accounting.autoSync'), subtitle: translate('workspace.qbo.advancedConfig.autoSyncDescription'), switchAccessibilityLabel: translate('workspace.qbo.advancedConfig.autoSyncDescription'), isActive: !!autoSync?.enabled, @@ -106,7 +106,7 @@ function QuickbooksAdvancedPage({policy}: WithPolicyConnectionsProps) { wrapperStyle: styles.mv3, }, { - title: translate('workspace.qbo.advancedConfig.reimbursedReports'), + title: translate('workspace.accounting.reimbursedReports'), subtitle: translate('workspace.qbo.advancedConfig.reimbursedReportsDescription'), switchAccessibilityLabel: translate('workspace.qbo.advancedConfig.reimbursedReportsDescription'), isActive: isSyncReimbursedSwitchOn, @@ -136,7 +136,7 @@ function QuickbooksAdvancedPage({policy}: WithPolicyConnectionsProps) { shouldEnableMaxHeight testID={QuickbooksAdvancedPage.displayName} > - + {qboToggleSettingItems.map((item) => ( diff --git a/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx b/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx index c950a745e73a..89b70fe5ce97 100644 --- a/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx +++ b/src/pages/workspace/accounting/xero/advanced/XeroAdvancedPage.tsx @@ -41,7 +41,7 @@ function XeroAdvancedPage({policy}: WithPolicyConnectionsProps) { return ( Policy.clearXeroErrorField(policyID, CONST.XERO_CONFIG.AUTO_SYNC)} /> ; @@ -70,6 +78,8 @@ function ToggleSettingOptionRow({ subtitle, switchAccessibilityLabel, shouldPlaceSubtitleBelowSwitch, + shouldEscapeText = undefined, + shouldParseSubtitle = false, wrapperStyle, titleStyle, onToggle, @@ -84,10 +94,34 @@ function ToggleSettingOptionRow({ }: ToggleSettingOptionRowProps) { const styles = useThemeStyles(); - const subTitleView = useMemo( - () => {subtitle}, - [shouldPlaceSubtitleBelowSwitch, subtitle, styles.mr5, styles.mt1, styles.textLabel, styles.textSupporting], - ); + const subtitleHtml = useMemo(() => { + if (!subtitle || !shouldParseSubtitle) { + return ''; + } + const parser = new ExpensiMark(); + return parser.replace(subtitle, {shouldEscapeText}); + }, [subtitle, shouldParseSubtitle, shouldEscapeText]); + + const processedSubtitle = useMemo(() => { + let textToWrap = ''; + + if (shouldParseSubtitle) { + textToWrap = subtitleHtml; + } + + return textToWrap ? `${textToWrap}` : ''; + }, [shouldParseSubtitle, subtitleHtml]); + + const subTitleView = useMemo(() => { + if (!!subtitle && shouldParseSubtitle) { + return ( + + + + ); + } + return {subtitle}; + }, [subtitle, shouldParseSubtitle, styles.mutedNormalTextLabel, styles.mt1, styles.mr5, styles.flexRow, styles.renderHTML, shouldPlaceSubtitleBelowSwitch, processedSubtitle]); return ( ; /** Whther to automatically create employees and vendors upon export in NetSuite if they don't exist */ autoCreateEntities: boolean; @@ -934,7 +934,7 @@ type NetSuiteConnection = { source: JobSourceValues; /** Config object used solely to store autosync settings */ - config: { + config: OnyxCommon.OnyxValueWithOfflineFeedback<{ /** NetSuite auto synchronization configs */ autoSync: { /** Whether data should be automatically synched between the app and NetSuite */ @@ -943,7 +943,13 @@ type NetSuiteConnection = { /** The bedrock job associated with the NetSuite Auto Sync job */ jobID: string; }; - }; + + /** Collection of errors coming from BE */ + errors?: OnyxCommon.Errors; + + /** Collection of form field errors */ + errorFields?: OnyxCommon.ErrorFields; + }>; /** Encrypted token secret for authenticating to NetSuite */ tokenSecret: string;