diff --git a/src/CONST/index.ts b/src/CONST/index.ts index ea3c1dfcb8ec..5b41d882b79e 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3957,6 +3957,7 @@ const CONST = { ZENEFITS_SYNC_TITLE: 'zenefitsSyncTitle', ZENEFITS_SYNC_LOAD_DATA: 'zenefitsSyncLoadData', ZENEFITS_SYNC_PROVISIONING: 'zenefitsSyncProvisioning', + MERGE_HR_SYNC_TITLE: 'mergeHRSyncTitle', FINANCIAL_FORCE_SYNC_CONNECTION: 'financialForceSyncConnection', }, SYNC_STAGE_TIMEOUT_MINUTES: 20, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ab1332db0b2d..8397e407eeaa 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -326,6 +326,9 @@ const ONYXKEYS = { /** Token needed to initialize Plaid link */ RAM_ONLY_PLAID_LINK_TOKEN: 'plaidLinkToken', + /** Token needed to initialize the Merge Link SDK for HR integrations */ + RAM_ONLY_MERGE_HR_LINK_TOKEN: 'mergeHRLinkToken', + /** Capture Plaid event */ PLAID_CURRENT_EVENT: 'plaidCurrentEvent', @@ -1436,6 +1439,7 @@ type OnyxValuesMapping = { [ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData; [ONYXKEYS.IS_PLAID_DISABLED]: boolean; [ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN]: string; + [ONYXKEYS.RAM_ONLY_MERGE_HR_LINK_TOKEN]: string; [ONYXKEYS.ONFIDO_TOKEN]: string; [ONYXKEYS.ONFIDO_APPLICANT_ID]: string; [ONYXKEYS.NVP_PREFERRED_LOCALE]: OnyxTypes.Locale; diff --git a/src/libs/API/parameters/ConnectPolicyToMergeParams.ts b/src/libs/API/parameters/ConnectPolicyToMergeParams.ts new file mode 100644 index 000000000000..8f0dd5f67723 --- /dev/null +++ b/src/libs/API/parameters/ConnectPolicyToMergeParams.ts @@ -0,0 +1,11 @@ +import type {MergeHRProviderSlug} from '@src/CONST/MERGE_HR_PROVIDERS'; + +type ConnectPolicyToMergeParams = { + /** The ID of the policy to connect */ + policyID: string; + + /** The Merge HR provider slug identifying which HR system to integrate with via merge dev */ + integration: MergeHRProviderSlug; +}; + +export default ConnectPolicyToMergeParams; diff --git a/src/libs/API/parameters/SyncPolicyToMergeHRParams.ts b/src/libs/API/parameters/SyncPolicyToMergeHRParams.ts new file mode 100644 index 000000000000..93520791cb46 --- /dev/null +++ b/src/libs/API/parameters/SyncPolicyToMergeHRParams.ts @@ -0,0 +1,6 @@ +type SyncPolicyToMergeHRParams = { + /** The ID of the policy to sync */ + policyID: string; +}; + +export default SyncPolicyToMergeHRParams; diff --git a/src/libs/API/parameters/UpdateMergeHRApprovalModeParams.ts b/src/libs/API/parameters/UpdateMergeHRApprovalModeParams.ts new file mode 100644 index 000000000000..89c7c696be4a --- /dev/null +++ b/src/libs/API/parameters/UpdateMergeHRApprovalModeParams.ts @@ -0,0 +1,12 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + +type UpdateMergeHRApprovalModeParams = { + /** The ID of the policy to update */ + policyID: string; + + /** The new approval mode to apply to the Merge HR connection */ + approvalMode: ValueOf; +}; + +export default UpdateMergeHRApprovalModeParams; diff --git a/src/libs/API/parameters/UpdateMergeHRFinalApproverParams.ts b/src/libs/API/parameters/UpdateMergeHRFinalApproverParams.ts new file mode 100644 index 000000000000..6fc9b5dc7174 --- /dev/null +++ b/src/libs/API/parameters/UpdateMergeHRFinalApproverParams.ts @@ -0,0 +1,9 @@ +type UpdateMergeHRFinalApproverParams = { + /** The ID of the policy to update */ + policyID: string; + + /** Login of the member who will act as the final approver, or null to clear */ + finalApprover: string | null; +}; + +export default UpdateMergeHRFinalApproverParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 04289e934784..5c1003f18f93 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -23,10 +23,12 @@ export type {default as ConnectBankAccountParams} from './ConnectBankAccountPara export type {default as OpenDuplicatePolicyPageParams} from './OpenDuplicatePolicyPageParams'; export type {default as ConnectPolicyToAccountingIntegrationParams} from './ConnectPolicyToAccountingIntegrationParams'; export type {default as ConnectPolicyToGustoParams} from './ConnectPolicyToGustoParams'; +export type {default as ConnectPolicyToMergeParams} from './ConnectPolicyToMergeParams'; export type {default as ConnectPolicyToZenefitsParams} from './ConnectPolicyToZenefitsParams'; export type {default as OpenPolicyProfilePageParams} from './OpenPolicyProfilePageParams'; export type {default as OpenPolicyInitialPageParams} from './OpenPolicyInitialPageParams'; export type {default as SyncPolicyToGustoParams} from './SyncPolicyToGustoParams'; +export type {default as SyncPolicyToMergeHRParams} from './SyncPolicyToMergeHRParams'; export type {default as SyncPolicyToZenefitsParams} from './SyncPolicyToZenefitsParams'; export type {default as SyncPolicyToQuickbooksOnlineParams} from './SyncPolicyToQuickbooksOnlineParams'; export type {default as SyncPolicyToXeroParams} from './SyncPolicyToXeroParams'; @@ -255,6 +257,8 @@ export type {default as EnablePolicyReportFieldsParams} from './EnablePolicyRepo export type {default as EnablePolicyExpensifyCardsParams} from './EnablePolicyExpensifyCardsParams'; export type {default as UpdateGustoApprovalModeParams} from './UpdateGustoApprovalModeParams'; export type {default as UpdateGustoFinalApproverParams} from './UpdateGustoFinalApproverParams'; +export type {default as UpdateMergeHRApprovalModeParams} from './UpdateMergeHRApprovalModeParams'; +export type {default as UpdateMergeHRFinalApproverParams} from './UpdateMergeHRFinalApproverParams'; export type {default as UpdateZenefitsApprovalModeParams} from './UpdateZenefitsApprovalModeParams'; export type {default as UpdateZenefitsFinalApproverParams} from './UpdateZenefitsFinalApproverParams'; export type {default as AcceptJoinRequestParams} from './AcceptJoinRequest'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index cabe56a3585c..55b3cc496b03 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -387,6 +387,8 @@ const WRITE_COMMANDS = { UPGRADE_TO_CORPORATE: 'UpgradeToCorporate', UPDATE_GUSTO_APPROVAL_MODE: 'UpdateGustoApprovalMode', UPDATE_GUSTO_FINAL_APPROVER: 'UpdateGustoFinalApprover', + UPDATE_MERGE_HR_APPROVAL_MODE: 'UpdateMergeHRApprovalMode', + UPDATE_MERGE_HR_FINAL_APPROVER: 'UpdateMergeHRFinalApprover', UPDATE_ZENEFITS_APPROVAL_MODE: 'UpdateZenefitsApprovalMode', UPDATE_ZENEFITS_FINAL_APPROVER: 'UpdateZenefitsFinalApprover', DOWNGRADE_TO_TEAM: 'Policy_DowngradeToTeam', @@ -1009,6 +1011,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_PROMO_CODE]: Parameters.SetPromoCodeParams; [WRITE_COMMANDS.UPDATE_GUSTO_APPROVAL_MODE]: Parameters.UpdateGustoApprovalModeParams; [WRITE_COMMANDS.UPDATE_GUSTO_FINAL_APPROVER]: Parameters.UpdateGustoFinalApproverParams; + [WRITE_COMMANDS.UPDATE_MERGE_HR_APPROVAL_MODE]: Parameters.UpdateMergeHRApprovalModeParams; + [WRITE_COMMANDS.UPDATE_MERGE_HR_FINAL_APPROVER]: Parameters.UpdateMergeHRFinalApproverParams; [WRITE_COMMANDS.UPDATE_ZENEFITS_APPROVAL_MODE]: Parameters.UpdateZenefitsApprovalModeParams; [WRITE_COMMANDS.UPDATE_ZENEFITS_FINAL_APPROVER]: Parameters.UpdateZenefitsFinalApproverParams; [WRITE_COMMANDS.REQUEST_TAX_EXEMPTION]: null; @@ -1247,6 +1251,8 @@ const READ_COMMANDS = { CONNECT_POLICY_TO_XERO: 'ConnectPolicyToXero', CONNECT_POLICY_TO_GUSTO: 'ConnectPolicyToGusto', CONNECT_POLICY_TO_ZENEFITS: 'ConnectPolicyToZenefits', + CONNECT_POLICY_TO_MERGE: 'ConnectPolicyToMerge', + SYNC_POLICY_TO_MERGE_HR: 'SyncPolicyToMergeHR', SYNC_POLICY_TO_QUICKBOOKS_ONLINE: 'SyncPolicyToQuickbooksOnline', SYNC_POLICY_TO_XERO: 'SyncPolicyToXero', SYNC_POLICY_TO_NETSUITE: 'SyncPolicyToNetSuite', @@ -1348,6 +1354,8 @@ type ReadCommandParameters = { [READ_COMMANDS.CONNECT_POLICY_TO_XERO]: Parameters.ConnectPolicyToAccountingIntegrationParams; [READ_COMMANDS.CONNECT_POLICY_TO_GUSTO]: Parameters.ConnectPolicyToGustoParams; [READ_COMMANDS.CONNECT_POLICY_TO_ZENEFITS]: Parameters.ConnectPolicyToZenefitsParams; + [READ_COMMANDS.CONNECT_POLICY_TO_MERGE]: Parameters.ConnectPolicyToMergeParams; + [READ_COMMANDS.SYNC_POLICY_TO_MERGE_HR]: Parameters.SyncPolicyToMergeHRParams; [READ_COMMANDS.CONNECT_POLICY_TO_FINANCIAL_FORCE]: Parameters.ConnectPolicyToFinancialForceParams; [READ_COMMANDS.SYNC_POLICY_TO_QUICKBOOKS_ONLINE]: Parameters.SyncPolicyToQuickbooksOnlineParams; [READ_COMMANDS.SYNC_POLICY_TO_XERO]: Parameters.SyncPolicyToXeroParams; diff --git a/src/libs/ExportOnyxState/common.ts b/src/libs/ExportOnyxState/common.ts index 542a7f75a8ba..7f817ef0ced6 100644 --- a/src/libs/ExportOnyxState/common.ts +++ b/src/libs/ExportOnyxState/common.ts @@ -99,6 +99,7 @@ const onyxKeysToRemove = new Set | ValueOf> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + pendingFields: {integration: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + errorFields: {integration: null}, + }, + }, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.RAM_ONLY_MERGE_HR_LINK_TOKEN, + value: null, + }, + ]; + + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + pendingFields: {integration: null}, + errorFields: {integration: null}, + }, + }, + }, + }, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + pendingFields: {integration: null}, + errorFields: {integration: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + }, + }, + }, + ]; + + read( + READ_COMMANDS.CONNECT_POLICY_TO_MERGE, + { + policyID, + integration, + }, + {optimisticData, successData, failureData}, + ); +} + +/** + * Triggers a data sync for the Merge HR connection. + */ +function syncMergeHR(policyID: string) { + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`, + value: { + stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.MERGE_HR_SYNC_TITLE, + connectionName: CONST.POLICY.CONNECTIONS.NAME.MERGE_HR, + timestamp: new Date().toISOString(), + }, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`, + value: null, + }, + ]; + + read(READ_COMMANDS.SYNC_POLICY_TO_MERGE_HR, {policyID}, {optimisticData, failureData}); +} + +/** + * Updates the approval mode for the Merge HR connection. + */ +function updateMergeHRApprovalMode(policyID: string, approvalMode: ValueOf, currentApprovalMode?: ValueOf | null) { + const previousApprovalMode = currentApprovalMode ?? null; + + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + approvalMode, + pendingFields: {approvalMode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + errorFields: {approvalMode: null}, + }, + }, + }, + }, + }, + ]; + + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + pendingFields: {approvalMode: null}, + errorFields: {approvalMode: null}, + }, + }, + }, + }, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + approvalMode: previousApprovalMode, + pendingFields: {approvalMode: null}, + errorFields: {approvalMode: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + }, + }, + }, + ]; + + write( + WRITE_COMMANDS.UPDATE_MERGE_HR_APPROVAL_MODE, + { + policyID, + approvalMode, + }, + {optimisticData, successData, failureData}, + ); +} + +/** + * Updates the final approver for the Merge HR connection. + */ +function updateMergeHRFinalApprover(policyID: string, finalApprover: string | null, currentFinalApprover?: string | null) { + const previousFinalApprover = currentFinalApprover ?? null; + + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + finalApprover, + pendingFields: {finalApprover: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + errorFields: {finalApprover: null}, + }, + }, + }, + }, + }, + ]; + + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + pendingFields: {finalApprover: null}, + errorFields: {finalApprover: null}, + }, + }, + }, + }, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: { + config: { + finalApprover: previousFinalApprover, + pendingFields: {finalApprover: null}, + errorFields: {finalApprover: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}, + }, + }, + }, + }, + }, + ]; + + write( + WRITE_COMMANDS.UPDATE_MERGE_HR_FINAL_APPROVER, + { + policyID, + finalApprover, + }, + {optimisticData, successData, failureData}, + ); +} + +/** + * Removes the Merge HR connection from a policy. + */ +function disconnectMergeHR(policyID: string, currentConnection: Connections[typeof CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]) { + const optimisticData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: null, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policyID}`, + value: null, + }, + ]; + + const failureData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + connections: { + [CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]: currentConnection, + }, + }, + }, + ]; + + write(WRITE_COMMANDS.REMOVE_POLICY_CONNECTION, {policyID, connectionName: CONST.POLICY.CONNECTIONS.NAME.MERGE_HR}, {optimisticData, failureData}); +} + +export {connectPolicyToMergeHR, syncMergeHR, updateMergeHRApprovalMode, updateMergeHRFinalApprover, disconnectMergeHR}; diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts index 650919ddbe39..f05adad8d0a5 100644 --- a/src/libs/actions/connections/index.ts +++ b/src/libs/actions/connections/index.ts @@ -123,6 +123,9 @@ function getSyncConnectionParameters(connectionName: PolicyConnectionName) { case CONST.POLICY.CONNECTIONS.NAME.ZENEFITS: { return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_ZENEFITS, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.ZENEFITS_SYNC_TITLE}; } + case CONST.POLICY.CONNECTIONS.NAME.MERGE_HR: { + return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_MERGE_HR, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.MERGE_HR_SYNC_TITLE}; + } case CONST.POLICY.CONNECTIONS.NAME.CERTINIA: { return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_FINANCIAL_FORCE, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.FINANCIAL_FORCE_SYNC_CONNECTION}; } diff --git a/src/setup/index.ts b/src/setup/index.ts index 2bde36a8d857..7ee6c5dad5a3 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -68,6 +68,7 @@ export default function () { ONYXKEYS.RAM_ONLY_WALLET_ONFIDO, ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE, ONYXKEYS.RAM_ONLY_PLAID_LINK_TOKEN, + ONYXKEYS.RAM_ONLY_MERGE_HR_LINK_TOKEN, ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD, ], }); diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 166dda48a04a..8d873dc8a657 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1540,13 +1540,14 @@ type GustoConnectionConfig = HRConnectionConfigBase & { type MergeHRConnectionData = Record; /** Merge HR connection config */ -type MergeHRConnectionConfig = HRConnectionConfigBase & { - /** Integration provider slug */ - integration: MergeHRProviderSlug; +type MergeHRConnectionConfig = HRConnectionConfigBase & + OnyxCommon.OnyxValueWithOfflineFeedback<{ + /** Integration provider slug identifying which HR system is linked */ + integration: MergeHRProviderSlug; - /** Approval mode */ - approvalMode: ValueOf | null; -}; + /** Approval mode controlling how reports are routed for approval */ + approvalMode: ValueOf | null; + }>; /** TriNet (Zenefits) connection data */ type ZenefitsConnectionData = Record;