diff --git a/src/CONST.ts b/src/CONST.ts index b67e7085befc..8cc500f1c3a6 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2366,6 +2366,7 @@ const CONST = { SYNC_STAGE_NAME: { STARTING_IMPORT_QBO: 'startingImportQBO', STARTING_IMPORT_XERO: 'startingImportXero', + STARTING_IMPORT_QBD: 'startingImportQBD', QBO_IMPORT_MAIN: 'quickbooksOnlineImportMain', QBO_IMPORT_CUSTOMERS: 'quickbooksOnlineImportCustomers', QBO_IMPORT_EMPLOYEES: 'quickbooksOnlineImportEmployees', @@ -2382,6 +2383,17 @@ const CONST = { QBO_SYNC_APPLY_CUSTOMERS: 'quickbooksOnlineSyncApplyCustomers', QBO_SYNC_APPLY_PEOPLE: 'quickbooksOnlineSyncApplyEmployees', QBO_SYNC_APPLY_CLASSES_LOCATIONS: 'quickbooksOnlineSyncApplyClassesLocations', + QBD_IMPORT_TITLE: 'quickbooksDesktopImportTitle', + QBD_IMPORT_ACCOUNTS: 'quickbooksDesktopImportAccounts', + QBD_IMPORT_APPROVE_CERTIFICATE: 'quickbooksDesktopImportApproveCertificate', + QBD_IMPORT_DIMENSIONS: 'quickbooksDesktopImportDimensions', + QBD_IMPORT_CLASSES: 'quickbooksDesktopImportClasses', + QBD_IMPORT_CUSTOMERS: 'quickbooksDesktopImportCustomers', + QBD_IMPORT_VENDORS: 'quickbooksDesktopImportVendors', + QBD_IMPORT_EMPLOYEES: 'quickbooksDesktopImportEmployees', + QBD_IMPORT_MORE: 'quickbooksDesktopImportMore', + QBD_IMPORT_GENERIC: 'quickbooksDesktopImportSavePolicy', + QBD_WEB_CONNECTOR_REMINDER: 'quickbooksDesktopWebConnectorReminder', JOB_DONE: 'jobDone', XERO_SYNC_STEP: 'xeroSyncStep', XERO_SYNC_XERO_REIMBURSED_REPORTS: 'xeroSyncXeroReimbursedReports', diff --git a/src/languages/en.ts b/src/languages/en.ts index a3b8be3e971e..943d86050d97 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3558,6 +3558,7 @@ const translations = { other: 'Other integrations', syncNow: 'Sync now', disconnect: 'Disconnect', + reinstall: 'Reinstall connector', disconnectTitle: ({connectionName}: OptionalParam = {}) => { const integrationName = connectionName && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] : 'integration'; @@ -3606,14 +3607,18 @@ const translations = { syncStageName: ({stage}: SyncStageNameConnectionsParams) => { switch (stage) { case 'quickbooksOnlineImportCustomers': + case 'quickbooksDesktopImportCustomers': return 'Importing customers'; case 'quickbooksOnlineImportEmployees': case 'netSuiteSyncImportEmployees': case 'intacctImportEmployees': + case 'quickbooksDesktopImportEmployees': return 'Importing employees'; case 'quickbooksOnlineImportAccounts': + case 'quickbooksDesktopImportAccounts': return 'Importing accounts'; case 'quickbooksOnlineImportClasses': + case 'quickbooksDesktopImportClasses': return 'Importing classes'; case 'quickbooksOnlineImportLocations': return 'Importing locations'; @@ -3632,6 +3637,19 @@ const translations = { return 'Importing Xero data'; case 'startingImportQBO': return 'Importing QuickBooks Online data'; + case 'startingImportQBD': + case 'quickbooksDesktopImportMore': + return 'Importing QuickBooks Desktop data'; + case 'quickbooksDesktopImportTitle': + return 'Importing title'; + case 'quickbooksDesktopImportApproveCertificate': + return 'Importing approve ceritificate'; + case 'quickbooksDesktopImportDimensions': + return 'Importing dimensions'; + case 'quickbooksDesktopImportSavePolicy': + return 'Importing save policy'; + case 'quickbooksDesktopWebConnectorReminder': + return 'Still syncing data with QuickBooks... Please make sure the Web Connector is running'; case 'quickbooksOnlineSyncTitle': return 'Syncing QuickBooks Online data'; case 'quickbooksOnlineSyncLoadData': @@ -3705,6 +3723,7 @@ const translations = { case 'netSuiteSyncImportSubsidiaries': return 'Importing subsidiaries'; case 'netSuiteSyncImportVendors': + case 'quickbooksDesktopImportVendors': return 'Importing vendors'; case 'intacctCheckConnection': return 'Checking Sage Intacct connection'; diff --git a/src/languages/es.ts b/src/languages/es.ts index 8c84c9501f08..120214f80a18 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3565,6 +3565,7 @@ const translations = { other: 'Otras integraciones', syncNow: 'Sincronizar ahora', disconnect: 'Desconectar', + reinstall: 'Reinstalar el conector', disconnectTitle: ({connectionName}: OptionalParam = {}) => { const integrationName = connectionName && CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] ? CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName] : 'integración'; @@ -3612,14 +3613,18 @@ const translations = { syncStageName: ({stage}: SyncStageNameConnectionsParams) => { switch (stage) { case 'quickbooksOnlineImportCustomers': + case 'quickbooksDesktopImportCustomers': return 'Importando clientes'; case 'quickbooksOnlineImportEmployees': case 'netSuiteSyncImportEmployees': case 'intacctImportEmployees': + case 'quickbooksDesktopImportEmployees': return 'Importando empleados'; case 'quickbooksOnlineImportAccounts': + case 'quickbooksDesktopImportAccounts': return 'Importando cuentas'; case 'quickbooksOnlineImportClasses': + case 'quickbooksDesktopImportClasses': return 'Importando clases'; case 'quickbooksOnlineImportLocations': return 'Importando localidades'; @@ -3638,6 +3643,19 @@ const translations = { return 'Importando datos desde Xero'; case 'startingImportQBO': return 'Importando datos desde QuickBooks Online'; + case 'startingImportQBD': + case 'quickbooksDesktopImportMore': + return 'Importando datos desde QuickBooks Desktop'; + case 'quickbooksDesktopImportTitle': + return 'Importando título'; + case 'quickbooksDesktopImportApproveCertificate': + return 'Importando certificado de aprobación'; + case 'quickbooksDesktopImportDimensions': + return 'Importando dimensiones'; + case 'quickbooksDesktopImportSavePolicy': + return 'Importando política de guardado'; + case 'quickbooksDesktopWebConnectorReminder': + return 'Aún sincronizando datos con QuickBooks... Por favor, asegúrate de que el Conector Web esté en funcionamiento'; case 'quickbooksOnlineSyncTitle': return 'Sincronizando datos desde QuickBooks Online'; case 'quickbooksOnlineSyncLoadData': @@ -3705,6 +3723,7 @@ const translations = { case 'netSuiteSyncImportSubsidiaries': return 'Importando subsidiarias'; case 'netSuiteSyncImportVendors': + case 'quickbooksDesktopImportVendors': return 'Importando proveedores'; case 'netSuiteSyncExpensifyReimbursedReports': return 'Marcando facturas y recibos de NetSuite como pagados'; diff --git a/src/libs/API/parameters/SyncConnectionParams.ts b/src/libs/API/parameters/SyncConnectionParams.ts new file mode 100644 index 000000000000..35593535fd35 --- /dev/null +++ b/src/libs/API/parameters/SyncConnectionParams.ts @@ -0,0 +1,7 @@ +type SyncConnectionParams = { + policyID: string; + idempotencyKey: string; + forceDataRefresh?: boolean; +}; + +export default SyncConnectionParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index ddf10a138725..7bb450c8d781 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -334,4 +334,5 @@ export type {default as SetCompanyCardExportAccountParams} from './SetCompanyCar export type {default as SetMissingPersonalDetailsAndShipExpensifyCardParams} from './SetMissingPersonalDetailsAndShipExpensifyCardParams'; export type {default as SetInvoicingTransferBankAccountParams} from './SetInvoicingTransferBankAccountParams'; export type {default as ConnectPolicyToQuickBooksDesktopParams} from './ConnectPolicyToQuickBooksDesktopParams'; +export type {default as SyncConnectionParams} from './SyncConnectionParams'; export type {default as UpdateQuickbooksDesktopExpensesExportDestinationTypeParams} from './UpdateQuickbooksDesktopExpensesExportDestinationTypeParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index ee5b06aba6ab..5a90ca30e647 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -849,6 +849,7 @@ const READ_COMMANDS = { SYNC_POLICY_TO_XERO: 'SyncPolicyToXero', SYNC_POLICY_TO_NETSUITE: 'SyncPolicyToNetSuite', SYNC_POLICY_TO_SAGE_INTACCT: 'SyncPolicyToSageIntacct', + SYNC_POLICY_TO_QUICKBOOKS_DESKTOP: 'SyncPolicyToQuickbooksDesktop', OPEN_REIMBURSEMENT_ACCOUNT_PAGE: 'OpenReimbursementAccountPage', OPEN_WORKSPACE_VIEW: 'OpenWorkspaceView', GET_MAPBOX_ACCESS_TOKEN: 'GetMapboxAccessToken', diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index b93642a0fa5a..b6e8498f167c 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -3,7 +3,7 @@ import isObject from 'lodash/isObject'; import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {RemovePolicyConnectionParams, UpdateManyPolicyConnectionConfigurationsParams, UpdatePolicyConnectionConfigParams} from '@libs/API/parameters'; +import type {RemovePolicyConnectionParams, SyncConnectionParams, UpdateManyPolicyConnectionConfigurationsParams, UpdatePolicyConnectionConfigParams} from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import CONST from '@src/CONST'; @@ -163,6 +163,9 @@ function getSyncConnectionParameters(connectionName: PolicyConnectionName) { case CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT: { return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_SAGE_INTACCT, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.SAGE_INTACCT_SYNC_CHECK_CONNECTION}; } + case CONST.POLICY.CONNECTIONS.NAME.QBD: { + return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_QUICKBOOKS_DESKTOP, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.STARTING_IMPORT_QBD}; + } default: return undefined; } @@ -173,8 +176,9 @@ function getSyncConnectionParameters(connectionName: PolicyConnectionName) { * * @param policyID - ID of the policy for which the sync is needed * @param connectionName - Name of the connection, QBO/Xero + * @param forceDataRefresh - If true, it will trigger a full data refresh */ -function syncConnection(policyID: string, connectionName: PolicyConnectionName | undefined) { +function syncConnection(policyID: string, connectionName: PolicyConnectionName | undefined, forceDataRefresh = false) { if (!connectionName) { return; } @@ -203,17 +207,19 @@ function syncConnection(policyID: string, connectionName: PolicyConnectionName | }, ]; - API.read( - syncConnectionData.readCommand, - { - policyID, - idempotencyKey: policyID, - }, - { - optimisticData, - failureData, - }, - ); + const parameters: SyncConnectionParams = { + policyID, + idempotencyKey: policyID, + }; + + if (connectionName === CONST.POLICY.CONNECTIONS.NAME.QBD) { + parameters.forceDataRefresh = forceDataRefresh; + } + + API.read(syncConnectionData.readCommand, parameters, { + optimisticData, + failureData, + }); } function updateManyPolicyConnectionConfigs>( diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 0f02e350d91a..70692c0c84f4 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -104,10 +104,14 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { const tenants = useMemo(() => getXeroTenants(policy), [policy]); const currentXeroOrganization = findCurrentXeroOrganization(tenants, policy?.connections?.xero?.config?.tenantID); + const shouldShowSynchronizationError = !!synchronizationError; + const shouldShowEnterCredentialsMenuItem = + shouldShowEnterCredentials && (connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT || connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.NETSUITE); + const shouldShowReinstallConnectorMenuItem = shouldShowSynchronizationError && connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.QBD; const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( () => [ - ...(shouldShowEnterCredentials && (connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.SAGE_INTACCT || connectedIntegration === CONST.POLICY.CONNECTIONS.NAME.NETSUITE) + ...(shouldShowEnterCredentialsMenuItem ? [ { icon: Expensicons.Key, @@ -118,14 +122,28 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { iconRight: Expensicons.NewWindow, }, ] - : [ + : []), + ...(shouldShowReinstallConnectorMenuItem + ? [ + { + icon: Expensicons.CircularArrowBackwards, + text: translate('workspace.accounting.reinstall'), + onSelected: () => startIntegrationFlow({name: CONST.POLICY.CONNECTIONS.NAME.QBD}), + shouldCallAfterModalHide: true, + iconRight: Expensicons.NewWindow, + }, + ] + : []), + ...(!shouldShowEnterCredentialsMenuItem && !shouldShowReinstallConnectorMenuItem + ? [ { icon: Expensicons.Sync, text: translate('workspace.accounting.syncNow'), onSelected: () => syncConnection(policyID, connectedIntegration), disabled: isOffline, }, - ]), + ] + : []), { icon: Expensicons.Trashcan, text: translate('workspace.accounting.disconnect'), @@ -133,7 +151,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { shouldCallAfterModalHide: true, }, ], - [shouldShowEnterCredentials, translate, isOffline, policyID, connectedIntegration, startIntegrationFlow], + [shouldShowEnterCredentialsMenuItem, shouldShowReinstallConnectorMenuItem, translate, isOffline, policyID, connectedIntegration, startIntegrationFlow], ); useFocusEffect( @@ -269,7 +287,6 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { if (!connectedIntegration) { return []; } - const shouldShowSynchronizationError = !!synchronizationError; const shouldHideConfigurationOptions = isConnectionUnverified(policy, connectedIntegration); const integrationData = getAccountingIntegrationData(connectedIntegration, policyID, translate, policy, undefined, undefined, undefined, canUseNetSuiteUSATax); const iconProps = integrationData?.icon ? {icon: integrationData.icon, iconType: CONST.ICON_TYPE_AVATAR} : {}; @@ -364,6 +381,7 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { isSyncInProgress, connectedIntegration, synchronizationError, + shouldShowSynchronizationError, policyID, translate, styles.sectionMenuItemTopDescription, diff --git a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupFlowSyncPage.tsx b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupFlowSyncPage.tsx index 8091ff973fbd..6e2188ab3c65 100644 --- a/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupFlowSyncPage.tsx +++ b/src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupFlowSyncPage.tsx @@ -1,9 +1,38 @@ -import React from 'react'; -import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import type {StackScreenProps} from '@react-navigation/stack'; +import {useEffect} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import {isConnectionInProgress, syncConnection} from '@libs/actions/connections'; +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'; -function QuickBooksDesktopSetupFlowSyncPage() { - // TODO: [QBD] will be implemented in https://github.com/Expensify/App/issues/49698 - return ; +type QuickBooksDesktopSetupFlowSyncPageProps = StackScreenProps; + +function QuickBooksDesktopSetupFlowSyncPage({route}: QuickBooksDesktopSetupFlowSyncPageProps) { + const policyID: string = route.params.policyID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID ?? '-1'}`); + const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID ?? '-1'}`); + + useEffect(() => { + if (!policyID) { + return; + } + + const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy); + if (!isSyncInProgress) { + syncConnection(policyID, CONST.POLICY.CONNECTIONS.NAME.QBD, true); + } + + Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING.getRoute(policyID)); + + // disabling this rule, as we want this to run only on the first render + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + }, []); + + return null; } QuickBooksDesktopSetupFlowSyncPage.displayName = 'QuickBooksDesktopSetupFlowSyncPage';