diff --git a/src/CONST.ts b/src/CONST.ts index 11fbaf986663..8528ae57c32a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -636,6 +636,8 @@ const CONST = { 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', + SAGE_INTACCT_HELP_LINK: + "https://help.expensify.com/articles/expensify-classic/connections/sage-intacct/Sage-Intacct-Troubleshooting#:~:text=First%20make%20sure%20that%20you,your%20company's%20Web%20Services%20authorizations.", PRICING: `https://www.expensify.com/pricing`, // Use Environment.getEnvironmentURL to get the complete URL with port number diff --git a/src/components/ConnectToNetSuiteButton/index.tsx b/src/components/ConnectToNetSuiteButton/index.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.native.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/components/ConnectToQuickbooksOnlineButton/index.tsx b/src/components/ConnectToQuickbooksOnlineButton/index.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/components/ConnectToSageIntacctButton/index.tsx b/src/components/ConnectToSageIntacctButton/index.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/components/ConnectToSageIntacctFlow/index.tsx b/src/components/ConnectToSageIntacctFlow/index.tsx index 77a8c0280e37..20ed6fa79ebc 100644 --- a/src/components/ConnectToSageIntacctFlow/index.tsx +++ b/src/components/ConnectToSageIntacctFlow/index.tsx @@ -1,13 +1,16 @@ import React, {useEffect, useState} from 'react'; +import {useOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import PopoverMenu from '@components/PopoverMenu'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import {isAuthenticationError} from '@libs/actions/connections'; import {getAdminPoliciesConnectedToSageIntacct} from '@libs/actions/Policy/Policy'; import Navigation from '@libs/Navigation/Navigation'; import {useAccountingContext} from '@pages/workspace/accounting/AccountingContext'; import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; type ConnectToSageIntacctFlowProps = { @@ -23,6 +26,8 @@ function ConnectToSageIntacctFlow({policyID}: ConnectToSageIntacctFlowProps) { const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState({horizontal: 0, vertical: 0}); const {popoverAnchorRefs} = useAccountingContext(); const threeDotsMenuContainerRef = popoverAnchorRefs?.current?.[CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT]; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const shouldGoToEnterCredentials = isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT); const connectionOptions = [ { @@ -44,6 +49,10 @@ function ConnectToSageIntacctFlow({policyID}: ConnectToSageIntacctFlowProps) { ]; useEffect(() => { + if (shouldGoToEnterCredentials) { + Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ENTER_CREDENTIALS.getRoute(policyID)); + return; + } if (!hasPoliciesConnectedToSageIntacct) { Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.getRoute(policyID)); return; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index b115bbc58f01..d37bf32843d5 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -155,7 +155,7 @@ type MenuItemBaseProps = { shouldShowDescriptionOnTop?: boolean; /** Error to display at the bottom of the component */ - errorText?: string; + errorText?: string | ReactNode; /** Any additional styles to pass to error text. */ errorTextStyle?: StyleProp; diff --git a/src/languages/en.ts b/src/languages/en.ts index e14017c420e6..bd3689d564b9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2446,6 +2446,8 @@ export default { syncReimbursedReports: 'Sync reimbursed reports', syncReimbursedReportsDescription: 'Any time a report is paid using Expensify ACH, the corresponding bill payment will be created in the Sage Intacct account below.', paymentAccount: 'Sage Intacct payment account', + authenticationError: 'Can’t connect to Sage Intacct due to an authentication error. ', + learnMore: 'Learn more.', }, netsuite: { subsidiary: 'Subsidiary', diff --git a/src/languages/es.ts b/src/languages/es.ts index b0dad8b3981c..88361de07f10 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2490,6 +2490,8 @@ export default { syncReimbursedReportsDescription: 'Cuando un informe se reembolsa utilizando Expensify ACH, la factura de compra correspondiente se creará en la cuenta de Sage Intacct a continuación.', paymentAccount: 'Cuenta de pago Sage Intacct', + authenticationError: 'No se puede conectar a Sage Intacct debido a un error de autenticación. ', + learnMore: 'Más información.', }, netsuite: { subsidiary: 'Subsidiaria', diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 07ce5b1539df..f9acb67a1efb 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -29,7 +29,7 @@ import type { } from '@src/types/onyx/Policy'; import type PolicyEmployee from '@src/types/onyx/PolicyEmployee'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {getSynchronizationErrorMessage} from './actions/connections'; +import {hasSynchronizationErrorMessage} from './actions/connections'; import * as Localize from './Localize'; import Navigation from './Navigation/Navigation'; import * as NetworkStore from './Network/NetworkStore'; @@ -90,7 +90,7 @@ function hasPolicyCategoriesError(policyCategories: OnyxEntry) * Checks if the policy had a sync error. */ function hasSyncError(policy: OnyxEntry, isSyncInProgress: boolean): boolean { - return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!getSynchronizationErrorMessage(policy, connection, isSyncInProgress)); + return (Object.keys(policy?.connections ?? {}) as ConnectionName[]).some((connection) => !!hasSynchronizationErrorMessage(policy, connection, isSyncInProgress)); } /** diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 4c7ec69e6134..5a373b302c7d 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -6,7 +6,6 @@ import * as API from '@libs/API'; import type {RemovePolicyConnectionParams, UpdateManyPolicyConnectionConfigurationsParams, UpdatePolicyConnectionConfigParams} from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; -import * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; @@ -370,25 +369,24 @@ function updateManyPolicyConnectionConfigs, connectionName: PolicyConnectionName, isSyncInProgress: boolean): string | undefined { - const syncError = Localize.translateLocal('workspace.accounting.syncError', connectionName); +function hasSynchronizationErrorMessage(policy: OnyxEntry, connectionName: PolicyConnectionName, isSyncInProgress: boolean): boolean { // NetSuite does not use the conventional lastSync object, so we need to check for lastErrorSyncDate if (connectionName === CONST.POLICY.CONNECTIONS.NAME.NETSUITE) { if ( !isSyncInProgress && (!!policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE].lastErrorSyncDate || policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]?.verified === false) ) { - return syncError; + return true; } - return; + return false; } const connection = policy?.connections?.[connectionName]; if (isSyncInProgress || isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful !== false || !connection?.lastSync?.errorDate) { - return; + return false; } - return `${syncError} ("${connection?.lastSync?.errorMessage}")`; + return true; } function isAuthenticationError(policy: OnyxEntry, connectionName: PolicyConnectionName) { @@ -469,10 +467,10 @@ export { updatePolicyConnectionConfig, updatePolicyXeroConnectionConfig, updateManyPolicyConnectionConfigs, - getSynchronizationErrorMessage, isAuthenticationError, syncConnection, copyExistingPolicyConnection, isConnectionUnverified, isConnectionInProgress, + hasSynchronizationErrorMessage, }; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index c727c2cf3496..c125052c6935 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -22,7 +22,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {getSynchronizationErrorMessage, isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections'; +import {isAuthenticationError, isConnectionInProgress, isConnectionUnverified, removePolicyConnection, syncConnection} from '@libs/actions/connections'; import { areSettingsInErrorFields, findCurrentXeroOrganization, @@ -43,7 +43,7 @@ import ROUTES from '@src/ROUTES'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {AccountingContextProvider, useAccountingContext} from './AccountingContext'; import type {MenuItemData, PolicyAccountingPageProps} from './types'; -import {getAccountingIntegrationData} from './utils'; +import {getAccountingIntegrationData, getSynchronizationErrorMessage} from './utils'; function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy.id}`); @@ -65,15 +65,11 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME); const connectedIntegration = getConnectedIntegration(policy, accountingIntegrations) ?? connectionSyncProgress?.connectionName; - const synchronizationError = connectedIntegration && getSynchronizationErrorMessage(policy, connectedIntegration, isSyncInProgress); + const synchronizationError = connectedIntegration && getSynchronizationErrorMessage(policy, connectedIntegration, isSyncInProgress, translate, styles); // Enter credentials item shouldn't be shown for SageIntacct and NetSuite integrations const shouldShowEnterCredentials = - connectedIntegration && - !!synchronizationError && - isAuthenticationError(policy, connectedIntegration) && - connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT && - connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.NETSUITE; + connectedIntegration && !!synchronizationError && isAuthenticationError(policy, connectedIntegration) && connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.NETSUITE; const policyID = policy?.id ?? '-1'; // Get the last successful date of the integration. Then, if `connectionSyncProgress` is the same integration displayed and the state is 'jobDone', get the more recent update time of the two. @@ -96,7 +92,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { shouldCallAfterModalHide: true, disabled: isOffline, iconRight: Expensicons.NewWindow, - shouldShowRightIcon: true, + shouldShowRightIcon: connectedIntegration !== CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT, }, ] : [ diff --git a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx index 71728d523a37..9814e5cef996 100644 --- a/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx +++ b/src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx @@ -16,7 +16,6 @@ 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/SageIntactCredentialsForm'; @@ -58,7 +57,7 @@ function EnterSageIntacctCredentialsPage({route}: SageIntacctPrerequisitesPagePr > Navigation.goBack(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.getRoute(policyID))} + onBackButtonPress={() => Navigation.goBack()} /> , + connectionName: PolicyConnectionName, + isSyncInProgress: boolean, + translate: LocaleContextProps['translate'], + styles?: ThemeStyles, +): React.ReactNode | undefined { + const syncError = Localize.translateLocal('workspace.accounting.syncError', connectionName); + // NetSuite does not use the conventional lastSync object, so we need to check for lastErrorSyncDate + if (connectionName === CONST.POLICY.CONNECTIONS.NAME.NETSUITE) { + if ( + !isSyncInProgress && + (!!policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE].lastErrorSyncDate || policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.NETSUITE]?.verified === false) + ) { + return syncError; + } + return; + } + + const connection = policy?.connections?.[connectionName]; + if (isSyncInProgress || isEmptyObject(connection?.lastSync) || connection?.lastSync?.isSuccessful !== false || !connection?.lastSync?.errorDate) { + return; + } + + if (connectionName === CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT && isAuthenticationError(policy, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT)) { + return ( + + {translate('workspace.sageIntacct.authenticationError')} + + {translate('workspace.sageIntacct.learnMore')} + + + ); + } + return `${syncError} ("${connection?.lastSync?.errorMessage}")`; +} + +export {getAccountingIntegrationData, getSynchronizationErrorMessage};