Skip to content

Commit

Permalink
Merge pull request #45375 from mananjadhav/mj-netsuite-reuse-connection
Browse files Browse the repository at this point in the history
[#Wave-Control: Add NetSuite] Reuse Existing Connection for NetSuite and Sage Intacct
  • Loading branch information
yuwenmemon authored Jul 16, 2024
2 parents 5b35a06 + 95ab3d5 commit 3f76344
Show file tree
Hide file tree
Showing 16 changed files with 214 additions and 30 deletions.
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/netsuite/subsidiary-selector',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/subsidiary-selector` as const,
},
POLICY_ACCOUNTING_NETSUITE_EXISTING_CONNECTIONS: {
route: 'settings/workspaces/:policyID/accounting/netsuite/existing-connections',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/existing-connections` as const,
},
POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT: {
route: 'settings/workspaces/:policyID/accounting/netsuite/token-input',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/netsuite/token-input` as const,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ const SCREENS = {
NETSUITE_IMPORT_CUSTOM_SEGMENT_ADD: 'Policy_Accounting_NetSuite_Import_Custom_Segment_Add',
NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects',
NETSUITE_IMPORT_CUSTOMERS_OR_PROJECTS_SELECT: 'Policy_Accounting_NetSuite_Import_CustomersOrProjects_Select',
NETSUITE_REUSE_EXISTING_CONNECTIONS: 'Policy_Accounting_NetSuite_Reuse_Existing_Connections',
NETSUITE_TOKEN_INPUT: 'Policy_Accounting_NetSuite_Token_Input',
NETSUITE_SUBSIDIARY_SELECTOR: 'Policy_Accounting_NetSuite_Subsidiary_Selector',
NETSUITE_IMPORT: 'Policy_Accounting_NetSuite_Import',
Expand Down
81 changes: 77 additions & 4 deletions src/components/ConnectToNetSuiteButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React, {useState} from 'react';
import React, {useRef, useState} from 'react';
import type {View} from 'react-native';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
import PopoverMenu from '@components/PopoverMenu';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {removePolicyConnection} from '@libs/actions/connections';
import {getAdminPoliciesConnectedToNetSuite} from '@libs/actions/Policy/Policy';
import Navigation from '@libs/Navigation/Navigation';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {ConnectToNetSuiteButtonProps} from './types';
Expand All @@ -17,6 +23,30 @@ function ConnectToNetSuiteButton({policyID, shouldDisconnectIntegrationBeforeCon

const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);

const hasPoliciesConnectedToNetSuite = !!getAdminPoliciesConnectedToNetSuite()?.length;
const {isSmallScreenWidth} = useWindowDimensions();
const [isReuseConnectionsPopoverOpen, setIsReuseConnectionsPopoverOpen] = useState(false);
const [reuseConnectionPopoverPosition, setReuseConnectionPopoverPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0});
const threeDotsMenuContainerRef = useRef<View>(null);
const connectionOptions = [
{
icon: Expensicons.LinkCopy,
text: translate('workspace.common.createNewConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
},
},
{
icon: Expensicons.Copy,
text: translate('workspace.common.reuseExistingConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_EXISTING_CONNECTIONS.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
},
},
];

return (
<>
<Button
Expand All @@ -26,20 +56,63 @@ function ConnectToNetSuiteButton({policyID, shouldDisconnectIntegrationBeforeCon
return;
}

Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
if (!hasPoliciesConnectedToNetSuite) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
return;
}

if (!isSmallScreenWidth) {
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
setReuseConnectionPopoverPosition({
horizontal: x + width,
vertical: y + height,
});
});
}
setIsReuseConnectionsPopoverOpen(true);
}}
text={translate('workspace.accounting.setup')}
style={styles.justifyContentCenter}
small
isDisabled={isOffline}
ref={threeDotsMenuContainerRef}
/>
<PopoverMenu
isVisible={isReuseConnectionsPopoverOpen}
onClose={() => {
setIsReuseConnectionsPopoverOpen(false);
}}
withoutOverlay
menuItems={connectionOptions}
onItemSelected={(item) => {
if (!item?.onSelected) {
return;
}
item.onSelected();
}}
anchorPosition={reuseConnectionPopoverPosition}
anchorAlignment={{horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP}}
anchorRef={threeDotsMenuContainerRef}
/>
{shouldDisconnectIntegrationBeforeConnecting && isDisconnectModalOpen && integrationToDisconnect && (
<AccountingConnectionConfirmationModal
onConfirm={() => {
removePolicyConnection(policyID, integrationToDisconnect);

Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
setIsDisconnectModalOpen(false);

if (!hasPoliciesConnectedToNetSuite) {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.getRoute(policyID));
return;
}
if (!isSmallScreenWidth) {
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
setReuseConnectionPopoverPosition({
horizontal: x + width,
vertical: y + height,
});
});
}
setIsReuseConnectionsPopoverOpen(true);
}}
integrationToConnect={CONST.POLICY.CONNECTIONS.NAME.NETSUITE}
onCancel={() => setIsDisconnectModalOpen(false)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ConnectToSageIntacctButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ function ConnectToSageIntacctButton({policyID, shouldDisconnectIntegrationBefore
const connectionOptions = [
{
icon: Expensicons.LinkCopy,
text: translate('workspace.intacct.createNewConnection'),
text: translate('workspace.common.createNewConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_PREREQUISITES.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
},
},
{
icon: Expensicons.Copy,
text: translate('workspace.intacct.reuseExistingConnection'),
text: translate('workspace.common.reuseExistingConnection'),
onSelected: () => {
Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_EXISTING_CONNECTIONS.getRoute(policyID));
setIsReuseConnectionsPopoverOpen(false);
Expand Down
8 changes: 4 additions & 4 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,10 @@ export default {
lineItemLevel: 'Line-item level',
reportLevel: 'Report level',
appliedOnExport: 'Not imported into Expensify, applied on export',
createNewConnection: 'Create new connection',
reuseExistingConnection: 'Reuse existing connection',
existingConnections: 'Existing connections',
lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Last synced ${formattedDate}`,
},
qbo: {
importDescription: 'Choose which coding configurations to import from QuickBooks Online to Expensify.',
Expand Down Expand Up @@ -2595,10 +2599,6 @@ export default {
downloadExpensifyPackage: 'Download the Expensify package for Sage Intacct',
followSteps: 'Follow the steps in our How-to: Connect to Sage Intacct instructions',
enterCredentials: 'Enter your Sage Intacct credentials',
createNewConnection: 'Create new connection',
reuseExistingConnection: 'Reuse existing connection',
existingConnections: 'Existing connections',
sageIntacctLastSync: (formattedDate: string) => `Sage Intacct - Last synced ${formattedDate}`,
entity: 'Entity',
employeeDefault: 'Sage Intacct employee default',
employeeDefaultDescription: "The employee's default department will be applied to their expenses in Sage Intacct if one exists.",
Expand Down
8 changes: 4 additions & 4 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2096,6 +2096,10 @@ export default {
lineItemLevel: 'Nivel de partida',
reportLevel: 'Nivel de informe',
appliedOnExport: 'No se importa en Expensify, se aplica en la exportación',
createNewConnection: 'Crear una nueva conexión',
reuseExistingConnection: 'Reutilizar la conexión existente',
existingConnections: 'Conexiones existentes',
lastSyncDate: (connectionName: string, formattedDate: string) => `${connectionName} - Última sincronización ${formattedDate}`,
},
qbo: {
importDescription: 'Elige que configuraciónes de codificación son importadas desde QuickBooks Online a Expensify.',
Expand Down Expand Up @@ -2644,10 +2648,6 @@ export default {
downloadExpensifyPackage: 'Descargar el paquete Expensify para Sage Intacct',
followSteps: 'Siga los pasos de nuestras instrucciones Cómo: Instrucciones para conectarse a Sage Intacct',
enterCredentials: 'Introduzca sus credenciales de Sage Intacct',
createNewConnection: 'Crear una nueva conexión',
reuseExistingConnection: 'Reutilizar la conexión existente',
existingConnections: 'Conexiones existentes',
sageIntacctLastSync: (formattedDate: string) => `Sage Intacct - Última sincronización ${formattedDate}`,
entity: 'Entidad',
employeeDefault: 'Sage Intacct empleado por defecto',
employeeDefaultDescription: 'El departamento por defecto del empleado se aplicará a sus gastos en Sage Intacct si existe.',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {ConnectionName} from '@src/types/onyx/Policy';

type CopyExistingPolicyConnectionParams = {
policyID: string;
targetPolicyID: string;
connectionName: string;
connectionName: ConnectionName;
};

export default CopyExistingPolicyConnectionParams;
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
require<ReactComponentModule>('../../../../pages/workspace/accounting/xero/advanced/XeroBillPaymentAccountSelectorPage').default,

[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/NetSuiteSubsidiarySelector').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REUSE_EXISTING_CONNECTIONS]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteExistingConnectionsPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: () =>
require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage').default,
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/netsuite/import/NetSuiteImportPage').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_PAYMENT_ACCOUNT_SELECTOR,
SCREENS.WORKSPACE.ACCOUNTING.XERO_EXPORT_BANK_ACCOUNT_SELECT,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REUSE_EXISTING_CONNECTIONS,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT,
SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.ACCOUNTING.XERO_BILL_PAYMENT_ACCOUNT_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_XERO_BILL_PAYMENT_ACCOUNT_SELECTOR.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_TOKEN_INPUT.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REUSE_EXISTING_CONNECTIONS]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_EXISTING_CONNECTIONS.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_MAPPING]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_MAPPING.route},
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_IMPORT_CUSTOM_FIELD]: {path: ROUTES.POLICY_ACCOUNTING_NETSUITE_IMPORT_CUSTOM_FIELD_MAPPING.route},
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,9 @@ type SettingsNavigatorParamList = {
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_SUBSIDIARY_SELECTOR]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_REUSE_EXISTING_CONNECTIONS]: {
policyID: string;
};
[SCREENS.WORKSPACE.ACCOUNTING.NETSUITE_TOKEN_INPUT]: {
policyID: string;
};
Expand Down
5 changes: 5 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3081,6 +3081,10 @@ function getAdminPoliciesConnectedToSageIntacct(): Policy[] {
return Object.values(allPolicies ?? {}).filter<Policy>((policy): policy is Policy => !!policy && policy.role === CONST.POLICY.ROLE.ADMIN && !!policy?.connections?.intacct);
}

function getAdminPoliciesConnectedToNetSuite(): Policy[] {
return Object.values(allPolicies ?? {}).filter<Policy>((policy): policy is Policy => !!policy && policy.role === CONST.POLICY.ROLE.ADMIN && !!policy?.connections?.netsuite);
}

export {
leaveWorkspace,
addBillingCardAndRequestPolicyOwnerChange,
Expand Down Expand Up @@ -3151,6 +3155,7 @@ export {
upgradeToCorporate,
openPolicyExpensifyCardsPage,
requestExpensifyCardLimitIncrease,
getAdminPoliciesConnectedToNetSuite,
getAdminPoliciesConnectedToSageIntacct,
};

Expand Down
10 changes: 0 additions & 10 deletions src/libs/actions/connections/SageIntacct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,15 +748,6 @@ function updateSageIntacctEntity(policyID: string, entity: string) {
API.write(WRITE_COMMANDS.UPDATE_SAGE_INTACCT_ENTITY, parameters, prepareOnyxDataForConfigUpdate(policyID, CONST.SAGE_INTACCT_CONFIG.ENTITY, entity));
}

function reuseSageIntacctConnection(policyID: string, targetPolicyID: string, connectionName: string) {
const parameters = {
policyID,
targetPolicyID,
connectionName,
};
API.write(WRITE_COMMANDS.COPY_EXISTING_POLICY_CONNECTION, parameters, {});
}

export {
connectToSageIntacct,
updateSageIntacctBillable,
Expand All @@ -778,5 +769,4 @@ export {
updateSageIntacctSyncReimbursedReports,
updateSageIntacctSyncReimbursementAccountID,
updateSageIntacctEntity,
reuseSageIntacctConnection,
};
37 changes: 36 additions & 1 deletion src/libs/actions/connections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,39 @@ function hasSynchronizationError(policy: OnyxEntry<Policy>, connectionName: Poli
return !isSyncInProgress && policy?.connections?.[connectionName]?.lastSync?.isSuccessful === false;
}

export {removePolicyConnection, updatePolicyConnectionConfig, updateManyPolicyConnectionConfigs, hasSynchronizationError, syncConnection};
function copyExistingPolicyConnection(connectedPolicyID: string, targetPolicyID: string, connectionName: ConnectionName) {
let stageInProgress;
switch (connectionName) {
case CONST.POLICY.CONNECTIONS.NAME.NETSUITE:
stageInProgress = CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.NETSUITE_SYNC_CONNECTION;
break;
case CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT:
stageInProgress = CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.SAGE_INTACCT_SYNC_CHECK_CONNECTION;
break;
default:
stageInProgress = null;
}

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${targetPolicyID}`,
value: {
stageInProgress,
connectionName,
timestamp: new Date().toISOString(),
},
},
];
API.write(
WRITE_COMMANDS.COPY_EXISTING_POLICY_CONNECTION,
{
policyID: connectedPolicyID,
targetPolicyID,
connectionName,
},
{optimisticData},
);
}

export {removePolicyConnection, updatePolicyConnectionConfig, updateManyPolicyConnectionConfigs, hasSynchronizationError, syncConnection, copyExistingPolicyConnection};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import MenuItemList from '@components/MenuItemList';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import {reuseSageIntacctConnection} from '@libs/actions/connections/SageIntacct';
import {copyExistingPolicyConnection} from '@libs/actions/connections';
import {getAdminPoliciesConnectedToSageIntacct} from '@libs/actions/Policy/Policy';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
Expand All @@ -30,9 +30,9 @@ function ExistingConnectionsPage({route}: ExistingConnectionsPageProps) {
key: policy.id,
icon: policy.avatarURL ? policy.avatarURL : ReportUtils.getDefaultWorkspaceAvatar(policy.name),
iconType: policy.avatarURL ? CONST.ICON_TYPE_AVATAR : CONST.ICON_TYPE_WORKSPACE,
description: date ? translate('workspace.intacct.sageIntacctLastSync', date) : translate('workspace.accounting.intacct'),
description: date ? translate('workspace.common.lastSyncDate', CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY.intacct, date) : translate('workspace.accounting.intacct'),
onPress: () => {
reuseSageIntacctConnection(policy.id, policyID, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT);
copyExistingPolicyConnection(policy.id, policyID, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT);
Navigation.dismissModal();
},
};
Expand All @@ -45,7 +45,7 @@ function ExistingConnectionsPage({route}: ExistingConnectionsPageProps) {
testID={ExistingConnectionsPage.displayName}
>
<HeaderWithBackButton
title={translate('workspace.intacct.existingConnections')}
title={translate('workspace.common.existingConnections')}
shouldShowBackButton
onBackButtonPress={() => Navigation.goBack()}
/>
Expand Down
Loading

0 comments on commit 3f76344

Please sign in to comment.