diff --git a/src/CONST.ts b/src/CONST.ts index f1e4a3bb46d5..03089aab00ba 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2130,6 +2130,12 @@ const CONST = { NETSUITE: 'netsuite', SAGE_INTACCT: 'intacct', }, + ROUTE: { + QBO: 'quickbooks-online', + XERO: 'xero', + NETSUITE: 'netsuite', + SAGE_INTACCT: 'sage-intacct', + }, NAME_USER_FRIENDLY: { netsuite: 'NetSuite', quickbooksOnline: 'Quickbooks Online', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index de495568daa3..0ece7cc316aa 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -717,11 +717,12 @@ const ROUTES = { }, WORKSPACE_ACCOUNTING_CARD_RECONCILIATION: { route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation', - getRoute: (policyID: string, connection?: ConnectionName) => `settings/workspaces/${policyID}/accounting/${connection}/card-reconciliation` as const, + getRoute: (policyID: string, connection?: ValueOf) => `settings/workspaces/${policyID}/accounting/${connection}/card-reconciliation` as const, }, WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS: { route: 'settings/workspaces/:policyID/accounting/:connection/card-reconciliation/account', - getRoute: (policyID: string, connection?: ConnectionName) => `settings/workspaces/${policyID}/accounting/${connection}/card-reconciliation/account` as const, + getRoute: (policyID: string, connection?: ValueOf) => + `settings/workspaces/${policyID}/accounting/${connection}/card-reconciliation/account` as const, }, WORKSPACE_CATEGORIES: { route: 'settings/workspaces/:policyID/categories', diff --git a/src/libs/API/parameters/ToggleCardContinuousReconciliationParams.ts b/src/libs/API/parameters/ToggleCardContinuousReconciliationParams.ts new file mode 100644 index 000000000000..7433418058bd --- /dev/null +++ b/src/libs/API/parameters/ToggleCardContinuousReconciliationParams.ts @@ -0,0 +1,7 @@ +type ToggleCardContinuousReconciliationParams = { + workspaceAccountID: number; + shouldUseContinuousReconciliation: boolean; + expensifyCardContinuousReconciliationConnection?: string; +}; + +export default ToggleCardContinuousReconciliationParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 53b58d224e17..6cd7d04c3cfe 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -276,3 +276,4 @@ export type {default as ConfigureExpensifyCardsForPolicyParams} from './Configur export type {default as CreateExpensifyCardParams} from './CreateExpensifyCardParams'; export type {default as UpdateExpensifyCardTitleParams} from './UpdateExpensifyCardTitleParams'; export type {default as OpenCardDetailsPageParams} from './OpenCardDetailsPageParams'; +export type {default as ToggleCardContinuousReconciliationParams} from './ToggleCardContinuousReconciliationParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index ef39e9bf005b..109dd2a725f2 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -332,6 +332,7 @@ const WRITE_COMMANDS = { CONFIGURE_EXPENSIFY_CARDS_FOR_POLICY: 'ConfigureExpensifyCardsForPolicy', CREATE_EXPENSIFY_CARD: 'CreateExpensifyCard', CREATE_ADMIN_ISSUED_VIRTUAL_CARD: 'CreateAdminIssuedVirtualCard', + TOGGLE_CARD_CONTINUOUS_RECONCILIATION: 'ToggleCardContinuousReconciliation', } as const; type WriteCommand = ValueOf; @@ -669,6 +670,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.CONFIGURE_EXPENSIFY_CARDS_FOR_POLICY]: Parameters.ConfigureExpensifyCardsForPolicyParams; [WRITE_COMMANDS.CREATE_EXPENSIFY_CARD]: Parameters.CreateExpensifyCardParams; [WRITE_COMMANDS.CREATE_ADMIN_ISSUED_VIRTUAL_CARD]: Omit; + [WRITE_COMMANDS.TOGGLE_CARD_CONTINUOUS_RECONCILIATION]: Parameters.ToggleCardContinuousReconciliationParams; }; const READ_COMMANDS = { diff --git a/src/libs/AccountingUtils.ts b/src/libs/AccountingUtils.ts new file mode 100644 index 000000000000..fe472752978a --- /dev/null +++ b/src/libs/AccountingUtils.ts @@ -0,0 +1,27 @@ +import type {ValueOf} from 'type-fest'; +import CONST from '@src/CONST'; +import type {ConnectionName} from '@src/types/onyx/Policy'; + +const ROUTE_NAME_MAPPING = { + [CONST.POLICY.CONNECTIONS.ROUTE.QBO]: CONST.POLICY.CONNECTIONS.NAME.QBO, + [CONST.POLICY.CONNECTIONS.ROUTE.XERO]: CONST.POLICY.CONNECTIONS.NAME.XERO, + [CONST.POLICY.CONNECTIONS.ROUTE.SAGE_INTACCT]: CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT, + [CONST.POLICY.CONNECTIONS.ROUTE.NETSUITE]: CONST.POLICY.CONNECTIONS.NAME.NETSUITE, +}; + +const NAME_ROUTE_MAPPING = { + [CONST.POLICY.CONNECTIONS.NAME.QBO]: CONST.POLICY.CONNECTIONS.ROUTE.QBO, + [CONST.POLICY.CONNECTIONS.NAME.XERO]: CONST.POLICY.CONNECTIONS.ROUTE.XERO, + [CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT]: CONST.POLICY.CONNECTIONS.ROUTE.SAGE_INTACCT, + [CONST.POLICY.CONNECTIONS.NAME.NETSUITE]: CONST.POLICY.CONNECTIONS.ROUTE.NETSUITE, +}; + +function getConnectionNameFromRouteParam(routeParam: ValueOf) { + return ROUTE_NAME_MAPPING[routeParam]; +} + +function getRouteParamForConnection(connectionName: ConnectionName) { + return NAME_ROUTE_MAPPING[connectionName]; +} + +export {getConnectionNameFromRouteParam, getRouteParamForConnection}; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 8bd1c44568c4..0d8d363f5b9b 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -620,11 +620,11 @@ type SettingsNavigatorParamList = { }; [SCREENS.WORKSPACE.ACCOUNTING.CARD_RECONCILIATION]: { policyID: string; - connection: ValueOf; + connection: ValueOf; }; [SCREENS.WORKSPACE.ACCOUNTING.RECONCILIATION_ACCOUNT_SETTINGS]: { policyID: string; - connection: ValueOf; + connection: ValueOf; }; [SCREENS.GET_ASSISTANCE]: { backTo: Routes; diff --git a/src/libs/actions/Card.ts b/src/libs/actions/Card.ts index 2356f125ff67..4430730f7222 100644 --- a/src/libs/actions/Card.ts +++ b/src/libs/actions/Card.ts @@ -18,6 +18,7 @@ import * as NetworkStore from '@libs/Network/NetworkStore'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ExpensifyCardDetails, IssueNewCardData, IssueNewCardStep} from '@src/types/onyx/Card'; +import type {ConnectionName} from '@src/types/onyx/Policy'; type ReplacementReason = 'damaged' | 'stolen'; @@ -497,6 +498,64 @@ function openCardDetailsPage(cardID: number) { API.read(READ_COMMANDS.OPEN_CARD_DETAILS_PAGE, parameters); } +function toggleContinuousReconciliation(workspaceAccountID: number, shouldUseContinuousReconciliation: boolean, connectionName: ConnectionName, oldConnectionName?: ConnectionName) { + const parameters = shouldUseContinuousReconciliation + ? { + workspaceAccountID, + shouldUseContinuousReconciliation, + expensifyCardContinuousReconciliationConnection: connectionName, + } + : { + workspaceAccountID, + shouldUseContinuousReconciliation, + }; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: shouldUseContinuousReconciliation, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: connectionName, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: shouldUseContinuousReconciliation, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: connectionName, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`, + value: !shouldUseContinuousReconciliation, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`, + value: oldConnectionName ?? null, + }, + ]; + + API.write(WRITE_COMMANDS.TOGGLE_CARD_CONTINUOUS_RECONCILIATION, parameters, { + optimisticData, + successData, + failureData, + }); +} + export { requestReplacementExpensifyCard, activatePhysicalExpensifyCard, @@ -513,5 +572,6 @@ export { configureExpensifyCardsForPolicy, issueExpensifyCard, openCardDetailsPage, + toggleContinuousReconciliation, }; export type {ReplacementReason}; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index ffde9cb25de0..9daec587d104 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -106,7 +106,7 @@ function accountingIntegrationData( ), onImportPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT.getRoute(policyID)), onExportPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_EXPORT.getRoute(policyID)), - onCardReconciliationPagePress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_CARD_RECONCILIATION.getRoute(policyID, CONST.POLICY.CONNECTIONS.NAME.QBO)), + onCardReconciliationPagePress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_CARD_RECONCILIATION.getRoute(policyID, CONST.POLICY.CONNECTIONS.ROUTE.QBO)), onAdvancedPagePress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED.getRoute(policyID)), }; case CONST.POLICY.CONNECTIONS.NAME.XERO: @@ -187,7 +187,7 @@ function accountingIntegrationData( ? CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_VENDOR : CONST.SAGE_INTACCT_CONFIG.NON_REIMBURSABLE_CREDIT_CARD_VENDOR, ], - onCardReconciliationPagePress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_CARD_RECONCILIATION.getRoute(policyID, CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT)), + onCardReconciliationPagePress: () => Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_CARD_RECONCILIATION.getRoute(policyID, CONST.POLICY.CONNECTIONS.ROUTE.SAGE_INTACCT)), onAdvancedPagePress: () => Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED.getRoute(policyID)), subscribedAdvancedSettings: [ CONST.SAGE_INTACCT_CONFIG.AUTO_SYNC_ENABLED, diff --git a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx index 5d469baa11ca..c907095c4301 100644 --- a/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx +++ b/src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx @@ -9,16 +9,19 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as AccountingUtils from '@libs/AccountingUtils'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections'; import withPolicyConnections from '@pages/workspace/withPolicyConnections'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; +import * as Card from '@userActions/Card'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {ConnectionName} from '@src/types/onyx/Policy'; type CardReconciliationPageProps = WithPolicyConnectionsProps & StackScreenProps; @@ -26,9 +29,11 @@ function CardReconciliationPage({policy, route}: CardReconciliationPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [reconciliationConnection] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${policy?.workspaceAccountID}`); - const [isContinuousReconciliationOn] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${policy?.workspaceAccountID}`); - const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${policy?.workspaceAccountID}`); + const workspaceAccountID = policy?.workspaceAccountID ?? -1; + + const [isContinuousReconciliationOn] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`); + const [cardSettings] = useOnyx(`${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`); + const [currentConnectionName] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); const paymentBankAccountID = cardSettings?.paymentBankAccountID ?? 0; @@ -36,28 +41,28 @@ function CardReconciliationPage({policy, route}: CardReconciliationPageProps) { const policyID = policy?.id ?? '-1'; const {connection} = route.params; - const autoSync = !!policy?.connections?.[connection]?.config?.autoSync?.enabled; + const connectionName = AccountingUtils.getConnectionNameFromRouteParam(connection) as ConnectionName; + const autoSync = !!policy?.connections?.[connectionName]?.config?.autoSync?.enabled; - // eslint-disable-next-line rulesdir/prefer-early-return - const toggleContinuousReconciliation = () => { - if (!isContinuousReconciliationOn) { + const toggleContinuousReconciliation = (value: boolean) => { + Card.toggleContinuousReconciliation(workspaceAccountID, value, connectionName, currentConnectionName); + if (value) { Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS.getRoute(policyID, connection)); } - // TODO: add API call when it's supported https://github.com/Expensify/Expensify/issues/407834 }; const navigateToAdvancedSettings = useCallback(() => { switch (connection) { - case CONST.POLICY.CONNECTIONS.NAME.QBO: + case CONST.POLICY.CONNECTIONS.ROUTE.QBO: Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_QUICKBOOKS_ONLINE_ADVANCED.getRoute(policyID)); break; - case CONST.POLICY.CONNECTIONS.NAME.XERO: + case CONST.POLICY.CONNECTIONS.ROUTE.XERO: Navigation.navigate(ROUTES.POLICY_ACCOUNTING_XERO_ADVANCED.getRoute(policyID)); break; - case CONST.POLICY.CONNECTIONS.NAME.NETSUITE: + case CONST.POLICY.CONNECTIONS.ROUTE.NETSUITE: Navigation.navigate(ROUTES.POLICY_ACCOUNTING_NETSUITE_ADVANCED.getRoute(policyID)); break; - case CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT: + case CONST.POLICY.CONNECTIONS.ROUTE.SAGE_INTACCT: Navigation.navigate(ROUTES.POLICY_ACCOUNTING_SAGE_INTACCT_ADVANCED.getRoute(policyID)); break; default: @@ -98,10 +103,10 @@ function CardReconciliationPage({policy, route}: CardReconciliationPageProps) { > {translate('workspace.accounting.autoSync').toLowerCase()} {' '} - {translate('common.conjunctionFor')} {CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connection]} + {translate('common.conjunctionFor')} {CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]} )} - {!!reconciliationConnection && ( + {!!paymentBankAccountID && isContinuousReconciliationOn && ( {translate('workspace.accounting.chooseReconciliationAccount.chooseBankAccount')} diff --git a/src/pages/workspace/expensifyCard/WorkspaceSettlementAccountPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceSettlementAccountPage.tsx index 815c3dedeb1b..b75226ed4636 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceSettlementAccountPage.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceSettlementAccountPage.tsx @@ -13,6 +13,7 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as AccountingUtils from '@libs/AccountingUtils'; import {getLastFourDigits} from '@libs/BankAccountUtils'; import * as CardUtils from '@libs/CardUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -25,6 +26,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {BankName} from '@src/types/onyx/Bank'; +import type {ConnectionName} from '@src/types/onyx/Policy'; type WorkspaceSettlementAccountPageProps = StackScreenProps; @@ -39,6 +41,9 @@ function WorkspaceSettlementAccountPage({route}: WorkspaceSettlementAccountPageP const [isUsedContinuousReconciliation] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION}${workspaceAccountID}`); const [reconciliationConnection] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION}${workspaceAccountID}`); + const connectionName = reconciliationConnection ?? ''; + const connectionParam = AccountingUtils.getRouteParamForConnection(connectionName as ConnectionName); + const paymentBankAccountID = cardSettings?.paymentBankAccountID ?? 0; const paymentBankAccountNumber = bankAccountsList?.[paymentBankAccountID.toString()]?.accountData?.accountNumber ?? ''; @@ -97,7 +102,7 @@ function WorkspaceSettlementAccountPage({route}: WorkspaceSettlementAccountPageP {isUsedContinuousReconciliation && ( {translate('workspace.expensifyCard.settlementAccountInfoPt1')}{' '} - Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS.getRoute(policyID, reconciliationConnection))}> + Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING_RECONCILIATION_ACCOUNT_SETTINGS.getRoute(policyID, connectionParam))}> {translate('workspace.expensifyCard.reconciliationAccount')} {' '} {`(${CONST.MASKED_PAN_PREFIX}${getLastFourDigits(paymentBankAccountNumber)}) `}