diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 2a55d6c548aa6..a650b5d4ac4f6 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -40,7 +40,7 @@ import DateUtils from './DateUtils'; import {translateLocal} from './Localize'; import Navigation from './Navigation/Navigation'; import {isOffline as isOfflineNetworkStore} from './Network/NetworkStore'; -import {getAccountIDsByLogins, getLoginByAccountID, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; +import {getAccountIDsByLogins, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; import {getAllSortedTransactions, getCategory, getTag} from './TransactionUtils'; import {isPublicDomain} from './ValidationUtils'; @@ -1121,44 +1121,6 @@ const sortWorkspacesBySelected = (workspace1: WorkspaceDetails, workspace2: Work return workspace1.name?.toLowerCase().localeCompare(workspace2.name?.toLowerCase() ?? '') ?? 0; }; -/** - * Determines whether the report can be moved to the workspace. - */ -const isWorkspaceEligibleForReportChange = (newPolicy: OnyxEntry, report: OnyxEntry, oldPolicy: OnyxEntry, currentUserLogin: string | undefined): boolean => { - const currentUserAccountID = getCurrentUserAccountID(); - const isCurrentUserMember = !!currentUserLogin && !!newPolicy?.employeeList?.[currentUserLogin]; - if (!isCurrentUserMember) { - return false; - } - - const isAdmin = isUserPolicyAdmin(newPolicy, currentUserLogin); - if (report?.stateNum && report?.stateNum > CONST.REPORT.STATE_NUM.SUBMITTED && !isAdmin) { - return false; - } - - // Submitters: workspaces where the submitter is a member of - const isCurrentUserSubmitter = report?.ownerAccountID === currentUserAccountID; - if (isCurrentUserSubmitter) { - return true; - } - - // Approvers: workspaces where both the approver AND submitter are members of - const reportApproverAccountID = getSubmitToAccountID(oldPolicy, report); - const isCurrentUserApprover = currentUserAccountID === reportApproverAccountID; - if (isCurrentUserApprover) { - const reportSubmitterLogin = report?.ownerAccountID ? getLoginByAccountID(report?.ownerAccountID) : undefined; - const isReportSubmitterMember = !!reportSubmitterLogin && !!newPolicy?.employeeList?.[reportSubmitterLogin]; - return isCurrentUserApprover && isReportSubmitterMember; - } - - // Admins: same as approvers OR workspaces where the admin is an admin of (note that the submitter is invited to the workspace in this case) - if (isPolicyOwner(newPolicy, currentUserAccountID) || isAdmin) { - return true; - } - - return false; -}; - /** * Takes removes pendingFields and errorFields from a customUnit */ @@ -1569,7 +1531,6 @@ export { getPolicyNameByID, getMostFrequentEmailDomain, getDescriptionForPolicyDomainCard, - isWorkspaceEligibleForReportChange, getManagerAccountID, isPrefferedExporter, areAllGroupPoliciesExpenseChatDisabled, diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index 7e614608a083d..3cce726915994 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -3,7 +3,7 @@ import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {Policy, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx'; import {isApprover as isApproverUtils} from './actions/Policy/Member'; -import {getCurrentUserAccountID, getCurrentUserEmail} from './actions/Report'; +import {getCurrentUserAccountID} from './actions/Report'; import { arePaymentsEnabled as arePaymentsEnabledUtils, getAllPolicies, @@ -12,9 +12,7 @@ import { getSubmitToAccountID, hasAccountingConnections, hasIntegrationAutoSync, - hasNoPolicyOtherThanPersonalType, isPrefferedExporter, - isWorkspaceEligibleForReportChange, } from './PolicyUtils'; import {getIOUActionForReportID, getReportActions, isPayAction} from './ReportActionsUtils'; import { @@ -29,8 +27,8 @@ import { isPayer as isPayerUtils, isProcessingReport as isProcessingReportUtils, isReportApproved as isReportApprovedUtils, - isReportManager as isReportManagerUtils, isSettled, + isWorkspaceEligibleForReportChange, } from './ReportUtils'; import {getSession} from './SessionUtils'; import {allHavePendingRTERViolation, isDuplicate, isOnHold as isOnHoldTransactionUtils, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils'; @@ -310,69 +308,10 @@ function isHoldActionForTransation(report: Report, reportTransaction: Transactio return isProcessingReport; } -function isChangeWorkspaceAction(report: Report, reportTransactions: Transaction[], violations: OnyxCollection, policy?: Policy): boolean { - const isExpenseReport = isExpenseReportUtils(report); - const isReportSubmitter = isCurrentUserSubmitter(report.reportID); - const areWorkflowsEnabled = !!(policy && policy.areWorkflowsEnabled); - const isClosedReport = isClosedReportUtils(report); - +function isChangeWorkspaceAction(report: Report): boolean { const policies = getAllPolicies(); - const currentUserEmail = getCurrentUserEmail(); - const policiesEligibleForChange = policies.filter((newPolicy) => isWorkspaceEligibleForReportChange(newPolicy, report, policy, currentUserEmail)); - - if (policiesEligibleForChange.length <= 1) { - return false; - } - - if (isExpenseReport && isReportSubmitter && !areWorkflowsEnabled && isClosedReport) { - return true; - } - - const isOpenReport = isOpenReportUtils(report); - const isProcessingReport = isProcessingReportUtils(report); - - if (isReportSubmitter && (isOpenReport || isProcessingReport)) { - return true; - } - - const isReportApprover = isApproverUtils(policy, getCurrentUserAccountID()); - - if (isReportApprover && isProcessingReport) { - return true; - } - - const isReportPayer = isPayerUtils(getSession(), report, false, policy); - const isReportApproved = isReportApprovedUtils({report}); - - if (isReportPayer && (isReportApproved || isClosedReport)) { - return true; - } - - const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const isReportReimbursed = isSettled(report); - const transactionIDs = reportTransactions.map((t) => t.transactionID); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations); - - const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, report, policy, violations); - - const userControlsReport = isReportSubmitter || isReportApprover || isAdmin; - const hasReceiptMatchViolation = hasAllPendingRTERViolations || (userControlsReport && shouldShowBrokenConnectionViolation); - const isReportExported = isExportedUtils(getReportActions(report)); - const isReportFinished = isReportApproved || isReportReimbursed || isClosedReport; - - if (isAdmin && ((!isReportExported && isReportFinished) || hasReceiptMatchViolation)) { - return true; - } - - const isIOUReport = isIOUReportUtils(report); - const hasOnlyPersonalWorkspace = hasNoPolicyOtherThanPersonalType(); - const isReportReceiver = isReportManagerUtils(report); - - if (isIOUReport && !hasOnlyPersonalWorkspace && isReportReceiver && isReportReimbursed) { - return true; - } - - return false; + const session = getSession(); + return policies.filter((newPolicy) => isWorkspaceEligibleForReportChange(newPolicy, report, session)).length > 0; } function isDeleteAction(report: Report, reportTransactions: Transaction[]): boolean { @@ -445,7 +384,7 @@ function getSecondaryReportActions( options.push(CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD); - if (isChangeWorkspaceAction(report, reportTransactions, violations, policy)) { + if (isChangeWorkspaceAction(report)) { options.push(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4186ce34ea3e8..7e6992bc36879 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -70,6 +70,7 @@ import type {Comment, TransactionChanges, WaypointCollection} from '@src/types/o import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; import {createDraftTransaction, getIOUReportActionToApproveOrPay, setMoneyRequestParticipants, unholdRequest} from './actions/IOU'; +import {isApprover as isApproverMember} from './actions/Policy/Member'; import {createDraftWorkspace} from './actions/Policy/Policy'; import {autoSwitchToFocusMode} from './actions/PriorityMode'; import {hasCreditBankAccount} from './actions/ReimbursementAccount/store'; @@ -102,6 +103,7 @@ import { getAccountIDsByLogins, getDisplayNameOrDefault, getEffectiveDisplayName, + getLoginByAccountID, getLoginsByAccountIDs, getPersonalDetailByEmail, getPersonalDetailsByIDs, @@ -10337,6 +10339,77 @@ function isExported(reportActions: OnyxEntry | ReportAction[]) { return Object.values(reportActions).some((action) => isExportIntegrationAction(action)); } +function verifyState(report: OnyxEntry, validStates: Array>): boolean { + if (report?.stateNum === undefined || report?.stateNum === null) { + return false; + } + return validStates.includes(report?.stateNum); +} + +function verifyStatus(report: OnyxEntry, validStatuses: Array>): boolean { + if (report?.statusNum === undefined || report?.statusNum === null) { + return false; + } + return validStatuses.includes(report?.statusNum); +} + +/** + * Determines whether the report can be moved to the workspace. + */ +function isWorkspaceEligibleForReportChange(newPolicy: OnyxEntry, report: OnyxEntry, session: OnyxEntry): boolean { + if (!session?.accountID) { + return false; + } + + const isIOU = isIOUReport(report); + const submitterLogin = report?.ownerAccountID && getLoginByAccountID(report?.ownerAccountID); + const isSubmitterMember = !!submitterLogin && !!newPolicy?.employeeList?.[submitterLogin]; + const managerLogin = report?.managerID && getLoginByAccountID(report?.managerID); + const isManagerMember = !!managerLogin && !!newPolicy?.employeeList?.[managerLogin]; + const isCurrentUserAdmin = isPolicyAdminPolicyUtils(newPolicy, session?.email); + const isPaidGroupPolicyType = isPaidGroupPolicyPolicyUtils(newPolicy); + const isReportOpenOrSubmitted = verifyState(report, [CONST.REPORT.STATE_NUM.OPEN, CONST.REPORT.STATE_NUM.SUBMITTED]); + + // For IOUs, the sender and receiver can only change the workspace if: + // 1. The sender AND receiver are both members of the new policy OR + // 2. The sender OR receiver is an admin of the new policy. In this case, changing the policy also invites the non-member to the policy + if (isIOU && isReportOpenOrSubmitted && isPaidGroupPolicyType && ((isSubmitterMember && isManagerMember) || isCurrentUserAdmin)) { + return true; + } + + // From this point on, reports must be of type Expense, the policy must be a paid type. + // The submitter and manager must also be policy members OR the current user is an admin so they can invite the non-members to the policy. + const isExpenseReportType = isExpenseReport(report); + if (!isExpenseReportType || !isPaidGroupPolicyType || !((isSubmitterMember && isManagerMember) || isCurrentUserAdmin)) { + return false; + } + + const isCurrentUserReportSubmitter = session.accountID === report?.ownerAccountID; + if (isCurrentUserReportSubmitter && isReportOpenOrSubmitted) { + return true; + } + + const isCurrentUserReportApprover = isApproverMember(newPolicy, session.accountID); + if (isCurrentUserReportApprover && verifyState(report, [CONST.REPORT.STATE_NUM.SUBMITTED])) { + return true; + } + + const isCurrentUserReportPayer = isPayer(session, report, false, newPolicy); + if (isCurrentUserReportPayer && verifyState(report, [CONST.REPORT.STATE_NUM.APPROVED])) { + return true; + } + + if ( + isCurrentUserAdmin && + verifyState(report, [CONST.REPORT.STATE_NUM.APPROVED]) && + verifyStatus(report, [CONST.REPORT.STATUS_NUM.APPROVED, CONST.REPORT.STATUS_NUM.REIMBURSED, CONST.REPORT.STATUS_NUM.CLOSED]) + ) { + return true; + } + + return false; +} + function getApprovalChain(policy: OnyxEntry, expenseReport: OnyxEntry): string[] { const approvalChain: string[] = []; const fullApprovalChain: string[] = []; @@ -10928,6 +11001,7 @@ export { generateReportAttributes, getReportPersonalDetailsParticipants, isAllowedToSubmitDraftExpenseReport, + isWorkspaceEligibleForReportChange, }; export type { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index e0fbc59291d32..95505d3951584 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4677,12 +4677,12 @@ function moveIOUReportToPolicy(reportID: string, policyID: string) { const changePolicyReportAction = buildOptimisticChangePolicyReportAction(iouReport.policyID, policyID, true); optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportId}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: {[changePolicyReportAction.reportActionID]: changePolicyReportAction}, }); successData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportId}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: { [changePolicyReportAction.reportActionID]: { ...changePolicyReportAction, @@ -4692,7 +4692,7 @@ function moveIOUReportToPolicy(reportID: string, policyID: string) { }); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportId}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: {[changePolicyReportAction.reportActionID]: null}, }); diff --git a/src/pages/ReportChangeWorkspacePage.tsx b/src/pages/ReportChangeWorkspacePage.tsx index e6bd240f803b4..5b09e6ad58a5a 100644 --- a/src/pages/ReportChangeWorkspacePage.tsx +++ b/src/pages/ReportChangeWorkspacePage.tsx @@ -16,8 +16,8 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportChangeWorkspaceNavigatorParamList} from '@libs/Navigation/types'; import {getLoginByAccountID} from '@libs/PersonalDetailsUtils'; -import {getPolicy, isPolicyAdmin, isPolicyMember, isWorkspaceEligibleForReportChange} from '@libs/PolicyUtils'; -import {isIOUReport, isMoneyRequestReport, isMoneyRequestReportPendingDeletion} from '@libs/ReportUtils'; +import {getPolicy, isPolicyAdmin, isPolicyMember} from '@libs/PolicyUtils'; +import {isIOUReport, isMoneyRequestReport, isMoneyRequestReportPendingDeletion, isWorkspaceEligibleForReportChange} from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -34,10 +34,9 @@ function ReportChangeWorkspacePage({report}: ReportChangeWorkspacePageProps) { const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const {translate} = useLocalize(); - const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const oldPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; - const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); + const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {canBeMissing: false}); const shouldShowLoadingIndicator = isLoadingApp && !isOffline; const selectPolicy = useCallback( @@ -48,22 +47,22 @@ function ReportChangeWorkspacePage({report}: ReportChangeWorkspacePageProps) { Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(reportID)); if (isIOUReport(reportID) && isPolicyAdmin(getPolicy(policyID)) && report.ownerAccountID && !isPolicyMember(getLoginByAccountID(report.ownerAccountID), policyID)) { moveIOUReportToPolicyAndInviteSubmitter(reportID, policyID); - } else if (isIOUReport(reportID) && isPolicyMember(currentUserLogin, policyID)) { + } else if (isIOUReport(reportID) && isPolicyMember(session?.email, policyID)) { moveIOUReportToPolicy(reportID, policyID); } else { changeReportPolicy(reportID, policyID); } }, - [currentUserLogin, report, reportID], + [session?.email, report, reportID], ); const {sections, shouldShowNoResultsFoundMessage, shouldShowSearchInput} = useWorkspaceList({ policies, - currentUserLogin, + currentUserLogin: session?.email, shouldShowPendingDeletePolicy: false, selectedPolicyID: report.policyID, searchTerm: debouncedSearchTerm, - additionalFilter: (newPolicy) => isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin), + additionalFilter: (newPolicy) => isWorkspaceEligibleForReportChange(newPolicy, report, session), }); if (!isMoneyRequestReport(report) || isMoneyRequestReportPendingDeletion(report) || (!report.total && !report.unheldTotal)) { diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index fdbe92c227de4..f0c51d3e0e9c7 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -10,9 +10,9 @@ import { getSubmitToAccountID, getUnitRateValue, isUserInvitedToWorkspace, - isWorkspaceEligibleForReportChange, shouldShowPolicy, } from '@libs/PolicyUtils'; +import {isWorkspaceEligibleForReportChange} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, PolicyEmployeeList, Report, Transaction} from '@src/types/onyx'; @@ -644,134 +644,167 @@ describe('PolicyUtils', () => { await waitForBatchedUpdatesWithAct(); }); - it('returns false if current user is not a member of the new policy', async () => { + it('returns true if report is IOU and both submitter and receiver are members of the policy', () => { + const currentUserLogin = employeeEmail; + const currentUserAccountID = employeeAccountID; + const session = {email: currentUserLogin, accountID: currentUserAccountID}; + + const newPolicy = { + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + employeeList: { + [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.USER}, + [approverEmail]: {email: approverEmail, role: CONST.POLICY.ROLE.USER}, + }, + }; + const report = { + ...createRandomReport(0), + type: CONST.REPORT.TYPE.IOU, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + ownerAccountID: currentUserAccountID, + managerID: approverAccountID, + }; + + const result = isWorkspaceEligibleForReportChange(newPolicy, report, session); + expect(result).toBe(true); + }); + + it('returns true if report is IOU and the submitter is not a member but the current user is a policy admin', () => { + const currentUserLogin = adminEmail; + const currentUserAccountID = adminAccountID; + const session = {email: currentUserLogin, accountID: currentUserAccountID}; + + const newPolicy = { + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + role: CONST.POLICY.ROLE.ADMIN, + employeeList: { + [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.ADMIN}, + }, + }; + const report = { + ...createRandomReport(0), + type: CONST.REPORT.TYPE.IOU, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + ownerAccountID: employeeAccountID, + }; + + const result = isWorkspaceEligibleForReportChange(newPolicy, report, session); + expect(result).toBe(true); + }); + + it('returns false if current user is not a member of the new policy', () => { const newPolicy = { ...createRandomPolicy(1), employeeList: {}, }; const report = createRandomReport(0); - const oldPolicy = createRandomPolicy(0); const currentUserLogin = 'nonmember@tests.com'; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: 0}); + const session = {email: currentUserLogin, accountID: 0}; - const result = isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin); + const result = isWorkspaceEligibleForReportChange(newPolicy, report, session); expect(result).toBe(false); }); - it('returns true if current user is the submitter', async () => { + it('returns true if current user is the submitter', () => { const currentUserLogin = employeeEmail; const currentUserAccountID = employeeAccountID; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: currentUserAccountID}); + const session = {email: currentUserLogin, accountID: currentUserAccountID}; const newPolicy = { - ...createRandomPolicy(1), + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), employeeList: { [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.USER}, + [approverEmail]: {email: approverEmail, role: CONST.POLICY.ROLE.USER}, }, }; - const oldPolicy = createRandomPolicy(0); const report = { ...createRandomReport(0), + type: CONST.REPORT.TYPE.EXPENSE, + stateNum: CONST.REPORT.STATE_NUM.OPEN, ownerAccountID: currentUserAccountID, + managerID: approverAccountID, }; - const result = isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin); + const result = isWorkspaceEligibleForReportChange(newPolicy, report, session); expect(result).toBe(true); }); - it('returns true if current user is a policy admin', async () => { + it('returns true if current user is a policy admin and the report is processing', () => { const currentUserLogin = adminEmail; const currentUserAccountID = adminAccountID; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: currentUserAccountID}); + const session = {email: currentUserLogin, accountID: currentUserAccountID}; const newPolicy = { - ...createRandomPolicy(1), + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + role: CONST.POLICY.ROLE.ADMIN, employeeList: { [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.ADMIN}, }, }; - const oldPolicy = createRandomPolicy(0); - const report = createRandomReport(0); - - const result = isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin); - expect(result).toBe(true); - }); - - it('returns true if current user is the policy owner', async () => { - const currentUserLogin = 'owner@test.com'; - const currentUserAccountID = ownerAccountID; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: currentUserAccountID}); - - const newPolicy = { - ...createRandomPolicy(1), + const report = { + ...createRandomReport(0), + type: CONST.REPORT.TYPE.EXPENSE, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, ownerAccountID: currentUserAccountID, - employeeList: { - [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.ADMIN}, - }, }; - const oldPolicy = createRandomPolicy(0); - const report = createRandomReport(0); - const result = isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin); + const result = isWorkspaceEligibleForReportChange(newPolicy, report, session); expect(result).toBe(true); }); - it('returns true if current user is the approver and submitter is a member', async () => { + it('returns true if current user is the approver and submitter is a member', () => { const currentUserLogin = approverEmail; const currentUserAccountID = approverAccountID; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: currentUserAccountID}); + const session = {email: currentUserLogin, accountID: currentUserAccountID}; const submitterLogin = employeeEmail; const submitterAccountID = employeeAccountID; const newPolicy = { - ...createRandomPolicy(1), + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + approver: approverEmail, employeeList: { [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.USER}, [submitterLogin]: {email: submitterLogin, role: CONST.POLICY.ROLE.USER}, }, }; - const oldPolicy = { - ...createRandomPolicy(0), - employeeList: { - [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.USER}, - [submitterLogin]: {email: submitterLogin, role: CONST.POLICY.ROLE.USER, submitsTo: currentUserLogin}, - }, - approver: currentUserLogin, - }; const report = { ...createRandomReport(0), + type: CONST.REPORT.TYPE.EXPENSE, + managerID: approverAccountID, ownerAccountID: submitterAccountID, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, }; - const result = isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin); + const result = isWorkspaceEligibleForReportChange(newPolicy, report, session); expect(result).toBe(true); }); - it('returns false if current user is approver but submitter not member', async () => { + it('returns false if current user is approver but submitter not member', () => { const currentUserLogin = approverEmail; const currentUserAccountID = approverAccountID; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: currentUserAccountID}); + const session = {email: currentUserLogin, accountID: currentUserAccountID}; const newPolicy = { - ...createRandomPolicy(1), + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), employeeList: { [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.USER}, }, }; const report = { ...createRandomReport(0), + type: CONST.REPORT.TYPE.EXPENSE, + managerID: approverAccountID, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, ownerAccountID: employeeAccountID, }; - const oldPolicy = createRandomPolicy(0); - expect(isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin)).toBe(false); + expect(isWorkspaceEligibleForReportChange(newPolicy, report, session)).toBe(false); }); - it('returns false if the report is approved and the current user is not an admin', async () => { + it('returns false if the report is approved and the current user is not an admin', () => { const currentUserLogin = approverEmail; const currentUserAccountID = approverAccountID; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: currentUserAccountID}); + const session = {email: currentUserLogin, accountID: currentUserAccountID}; const newPolicy = { ...createRandomPolicy(1), @@ -784,18 +817,18 @@ describe('PolicyUtils', () => { ownerAccountID: employeeAccountID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; - const oldPolicy = createRandomPolicy(0); - expect(isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin)).toBe(false); + expect(isWorkspaceEligibleForReportChange(newPolicy, report, session)).toBe(false); }); - it('returns true if the report is approved and the current user is an admin', async () => { + it('returns true if the report is approved and the current user is an admin', () => { const currentUserLogin = adminEmail; const currentUserAccountID = adminAccountID; - await Onyx.set(ONYXKEYS.SESSION, {email: currentUserLogin, accountID: currentUserAccountID}); + const session = {email: currentUserLogin, accountID: currentUserAccountID}; const newPolicy = { - ...createRandomPolicy(1), + ...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM), + role: CONST.POLICY.ROLE.ADMIN, employeeList: { [currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.ADMIN}, }, @@ -804,10 +837,11 @@ describe('PolicyUtils', () => { ...createRandomReport(0), ownerAccountID: employeeAccountID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, + type: CONST.REPORT.TYPE.EXPENSE, + managerID: approverAccountID, }; - const oldPolicy = createRandomPolicy(0); - expect(isWorkspaceEligibleForReportChange(newPolicy, report, oldPolicy, currentUserLogin)).toBe(true); + expect(isWorkspaceEligibleForReportChange(newPolicy, report, session)).toBe(true); }); }); diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index 335ea63d71fb2..cf946c16af949 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -4,23 +4,29 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx'; -const CURRENT_USER_ACCOUNT_ID = 1; -const CURRENT_USER_EMAIL = 'tester@mail.com'; -const OTHER_USER_EMAIL = 'tester1@mail.com'; +const EMPLOYEE_ACCOUNT_ID = 1; +const EMPLOYEE_EMAIL = 'employee@mail.com'; +const MANAGER_ACCOUNT_ID = 2; +const MANAGER_EMAIL = 'manager@mail.com'; +const APPROVER_ACCOUNT_ID = 3; +const APPROVER_EMAIL = 'approver@mail.com'; +const PAYER_ACCOUNT_ID = 4; +const PAYER_EMAIL = 'payer@mail.com'; +const ADMIN_ACCOUNT_ID = 5; +const ADMIN_EMAIL = 'admin@mail.com'; const SESSION = { - email: CURRENT_USER_EMAIL, - accountID: CURRENT_USER_ACCOUNT_ID, + email: EMPLOYEE_EMAIL, + accountID: EMPLOYEE_ACCOUNT_ID, }; const PERSONAL_DETAILS = { - accountID: CURRENT_USER_ACCOUNT_ID, - login: CURRENT_USER_EMAIL, + accountID: EMPLOYEE_ACCOUNT_ID, + login: EMPLOYEE_EMAIL, }; const REPORT_ID = 1; const POLICY_ID = 'POLICY_ID'; -const POLICY_ID2 = 'POLICY_ID2'; describe('getSecondaryAction', () => { beforeAll(() => { @@ -33,7 +39,7 @@ describe('getSecondaryAction', () => { jest.clearAllMocks(); Onyx.clear(); await Onyx.merge(ONYXKEYS.SESSION, SESSION); - await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {[CURRENT_USER_ACCOUNT_ID]: PERSONAL_DETAILS}); + await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {[EMPLOYEE_ACCOUNT_ID]: PERSONAL_DETAILS}); }); it('should always return default options', () => { @@ -48,7 +54,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS_NUM.OPEN, } as unknown as Report; @@ -68,13 +74,13 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, - managerID: CURRENT_USER_ACCOUNT_ID, + managerID: EMPLOYEE_ACCOUNT_ID, } as unknown as Report; const policy = { - approver: CURRENT_USER_EMAIL, + approver: EMPLOYEE_EMAIL, } as unknown as Policy; const TRANSACTION_ID = 'TRANSACTION_ID'; const transaction = { @@ -99,10 +105,12 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - managerID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + managerID: EMPLOYEE_ACCOUNT_ID, } as unknown as Report; - const policy = {} as unknown as Policy; + const policy = { + approver: EMPLOYEE_EMAIL, + } as unknown as Policy; const TRANSACTION_ID = 'TRANSACTION_ID'; const transaction = { @@ -125,10 +133,10 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS_NUM.OPEN, - managerID: CURRENT_USER_ACCOUNT_ID, + managerID: EMPLOYEE_ACCOUNT_ID, } as unknown as Report; const policy = {role: CONST.POLICY.ROLE.ADMIN} as unknown as Policy; const TRANSACTION_ID = 'TRANSACTION_ID'; @@ -152,11 +160,11 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, } as unknown as Report; - const policy = {approver: CURRENT_USER_EMAIL} as unknown as Policy; + const policy = {approver: EMPLOYEE_EMAIL} as unknown as Policy; const result = getSecondaryReportActions(report, [], {}, policy); expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.UNAPPROVE)).toBe(true); @@ -166,7 +174,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, } as unknown as Report; @@ -182,7 +190,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, isWaitingOnBankAccount: true, } as unknown as Report; @@ -219,7 +227,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.INVOICE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, } as unknown as Report; const policy = { connections: { @@ -236,7 +244,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, } as unknown as Report; @@ -254,7 +262,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, } as unknown as Report; @@ -271,7 +279,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.INVOICE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, } as unknown as Report; const policy = { connections: {[CONST.POLICY.CONNECTIONS.NAME.QBD]: {}}, @@ -286,7 +294,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, } as unknown as Report; @@ -304,7 +312,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, } as unknown as Report; @@ -321,12 +329,12 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, } as unknown as Report; const policy = { - connections: {[CONST.POLICY.CONNECTIONS.NAME.QBD]: {config: {export: {exporter: CURRENT_USER_EMAIL}, autoSync: {enabled: false}}}}, + connections: {[CONST.POLICY.CONNECTIONS.NAME.QBD]: {config: {export: {exporter: EMPLOYEE_EMAIL}, autoSync: {enabled: false}}}}, } as unknown as Policy; const result = getSecondaryReportActions(report, [], {}, policy); @@ -337,12 +345,12 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, } as unknown as Report; const policy = { - connections: {[CONST.POLICY.CONNECTIONS.NAME.QBD]: {config: {export: {exporter: OTHER_USER_EMAIL}, autoSync: {enabled: true}}}}, + connections: {[CONST.POLICY.CONNECTIONS.NAME.QBD]: {config: {export: {exporter: ADMIN_EMAIL}, autoSync: {enabled: true}}}}, role: CONST.POLICY.ROLE.ADMIN, } as unknown as Policy; @@ -354,7 +362,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, } as unknown as Report; @@ -368,27 +376,59 @@ describe('getSecondaryAction', () => { expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.HOLD)).toBe(true); }); - it('includes CHANGE_WORKSPACE option for closed expense report submitter', async () => { + it('includes CHANGE_WORKSPACE option for IOU and both submitter and payer members', async () => { const report = { reportID: REPORT_ID, - type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + type: CONST.REPORT.TYPE.IOU, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + managerID: MANAGER_ACCOUNT_ID, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, } as unknown as Report; - + const personalDetails = { + [EMPLOYEE_ACCOUNT_ID]: {login: EMPLOYEE_EMAIL}, + [MANAGER_ACCOUNT_ID]: {login: MANAGER_EMAIL}, + }; const policy = { id: POLICY_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - areWorkflowsEnabled: false, + type: CONST.POLICY.TYPE.TEAM, + employeeList: { + [EMPLOYEE_EMAIL]: {email: EMPLOYEE_EMAIL, role: CONST.POLICY.ROLE.USER}, + [MANAGER_EMAIL]: {email: MANAGER_EMAIL, role: CONST.POLICY.ROLE.USER}, + }, } as unknown as Policy; - const policy2 = { - id: POLICY_ID2, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(ONYXKEYS.SESSION, {email: EMPLOYEE_EMAIL, accountID: EMPLOYEE_ACCOUNT_ID}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); + + const result = getSecondaryReportActions(report, [], {}, policy); + expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true); + }); + + it('includes CHANGE_WORKSPACE option for IOU and the submitter is not a member but current user is admin', async () => { + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.IOU, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + managerID: MANAGER_ACCOUNT_ID, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + } as unknown as Report; + const personalDetails = { + [ADMIN_ACCOUNT_ID]: {login: ADMIN_EMAIL}, + [MANAGER_ACCOUNT_ID]: {login: MANAGER_EMAIL}, + }; + const policy = { + id: POLICY_ID, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.ADMIN, + employeeList: { + [ADMIN_EMAIL]: {email: ADMIN_EMAIL, role: CONST.POLICY.ROLE.ADMIN}, + }, + } as unknown as Policy; await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID2}`, policy2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(ONYXKEYS.SESSION, {email: EMPLOYEE_EMAIL, accountID: EMPLOYEE_ACCOUNT_ID}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); const result = getSecondaryReportActions(report, [], {}, policy); expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true); @@ -398,127 +438,170 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, statusNum: CONST.REPORT.STATUS_NUM.OPEN, stateNum: CONST.REPORT.STATE_NUM.OPEN, } as unknown as Report; + + const personalDetails = { + [ADMIN_ACCOUNT_ID]: {login: ADMIN_EMAIL}, + [MANAGER_ACCOUNT_ID]: {login: MANAGER_EMAIL}, + }; + const policy = { id: POLICY_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.ADMIN, + employeeList: { + [ADMIN_EMAIL]: {email: ADMIN_EMAIL, role: CONST.POLICY.ROLE.ADMIN}, + }, } as unknown as Policy; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); - - const policy2 = { - id: POLICY_ID2, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - }; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID2}`, policy2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(ONYXKEYS.SESSION, {email: EMPLOYEE_EMAIL, accountID: EMPLOYEE_ACCOUNT_ID}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); const result = getSecondaryReportActions(report, [], {}, policy); expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true); }); - it('includes CHANGE_WORKSPACE option for opened expense report submitter', async () => { + it('includes CHANGE_WORKSPACE option for submitter', async () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + managerID: MANAGER_ACCOUNT_ID, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, } as unknown as Report; + + const personalDetails = { + [EMPLOYEE_ACCOUNT_ID]: {login: EMPLOYEE_EMAIL}, + [MANAGER_ACCOUNT_ID]: {login: MANAGER_EMAIL}, + }; + const policy = { id: POLICY_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - approver: CURRENT_USER_EMAIL, + type: CONST.POLICY.TYPE.TEAM, + employeeList: { + [MANAGER_EMAIL]: {email: MANAGER_EMAIL, role: CONST.POLICY.ROLE.USER}, + [EMPLOYEE_EMAIL]: {email: EMPLOYEE_EMAIL, role: CONST.POLICY.ROLE.USER}, + }, } as unknown as Policy; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); - const policy2 = { - id: POLICY_ID2, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - }; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID2}`, policy2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(ONYXKEYS.SESSION, {email: EMPLOYEE_EMAIL, accountID: EMPLOYEE_ACCOUNT_ID}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); const result = getSecondaryReportActions(report, [], {}, policy); expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true); }); - it('includes CHANGE_WORKSPACE option for approved expense report payer', async () => { + it('includes CHANGE_WORKSPACE option for approver', async () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - statusNum: CONST.REPORT.STATUS_NUM.APPROVED, - stateNum: CONST.REPORT.STATE_NUM.APPROVED, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + managerID: MANAGER_ACCOUNT_ID, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, } as unknown as Report; + + const personalDetails = { + [EMPLOYEE_ACCOUNT_ID]: {login: EMPLOYEE_EMAIL}, + [MANAGER_ACCOUNT_ID]: {login: MANAGER_EMAIL}, + [APPROVER_ACCOUNT_ID]: {login: APPROVER_EMAIL}, + }; + const policy = { id: POLICY_ID, - employeeList: {[CURRENT_USER_EMAIL]: {role: CONST.POLICY.ROLE.ADMIN}}, - role: CONST.POLICY.ROLE.ADMIN, + type: CONST.POLICY.TYPE.TEAM, + approver: APPROVER_EMAIL, + employeeList: { + [MANAGER_EMAIL]: {email: MANAGER_EMAIL, role: CONST.POLICY.ROLE.USER}, + [EMPLOYEE_EMAIL]: {email: EMPLOYEE_EMAIL, role: CONST.POLICY.ROLE.USER}, + }, } as unknown as Policy; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); - const policy2 = { - id: POLICY_ID2, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - employeeList: {[CURRENT_USER_EMAIL]: {role: CONST.POLICY.ROLE.ADMIN}}, - role: CONST.POLICY.ROLE.ADMIN, - }; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID2}`, policy2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(ONYXKEYS.SESSION, {email: APPROVER_EMAIL, accountID: APPROVER_ACCOUNT_ID}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); const result = getSecondaryReportActions(report, [], {}, policy); expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true); }); - it('includes CHANGE_WORKSPACE option for not exported expense report admin', async () => { + it('includes CHANGE_WORKSPACE option for payer', async () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + managerID: MANAGER_ACCOUNT_ID, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, } as unknown as Report; + + const personalDetails = { + [EMPLOYEE_ACCOUNT_ID]: {login: EMPLOYEE_EMAIL}, + [MANAGER_ACCOUNT_ID]: {login: MANAGER_EMAIL}, + [APPROVER_ACCOUNT_ID]: {login: APPROVER_EMAIL}, + }; + const policy = { id: POLICY_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - role: CONST.POLICY.ROLE.ADMIN, + type: CONST.POLICY.TYPE.TEAM, + approver: APPROVER_EMAIL, + reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES, + // achAccount: { + // reimburser: PAYER_EMAIL, + // }, + employeeList: { + [MANAGER_EMAIL]: {email: MANAGER_EMAIL, role: CONST.POLICY.ROLE.USER}, + [EMPLOYEE_EMAIL]: {email: EMPLOYEE_EMAIL, role: CONST.POLICY.ROLE.USER}, + [PAYER_EMAIL]: {email: PAYER_EMAIL, role: CONST.POLICY.ROLE.USER}, + }, } as unknown as Policy; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); - const policy2 = { - id: POLICY_ID2, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - }; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID2}`, policy2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(ONYXKEYS.SESSION, {email: PAYER_EMAIL, accountID: PAYER_ACCOUNT_ID}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); const result = getSecondaryReportActions(report, [], {}, policy); expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true); }); - it('includes CHANGE_WORKSPACE option for IOU report receiver', async () => { + it('includes CHANGE_WORKSPACE option for admin', async () => { const report = { reportID: REPORT_ID, - type: CONST.REPORT.TYPE.IOU, - managerID: CURRENT_USER_ACCOUNT_ID, + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, + managerID: MANAGER_ACCOUNT_ID, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, } as unknown as Report; + + const personalDetails = { + [EMPLOYEE_ACCOUNT_ID]: {login: EMPLOYEE_EMAIL}, + [ADMIN_ACCOUNT_ID]: {login: ADMIN_EMAIL}, + }; + const policy = { id: POLICY_ID, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, + type: CONST.POLICY.TYPE.TEAM, + role: CONST.POLICY.ROLE.ADMIN, + employeeList: { + [ADMIN_EMAIL]: {email: ADMIN_EMAIL, role: CONST.POLICY.ROLE.ADMIN}, + }, } as unknown as Policy; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); - const policy2 = { - id: POLICY_ID2, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - employeeList: {[CURRENT_USER_EMAIL]: {}}, - }; - await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID2}`, policy2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(ONYXKEYS.SESSION, {email: ADMIN_EMAIL, accountID: ADMIN_ACCOUNT_ID}); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); const result = getSecondaryReportActions(report, [], {}, policy); expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true); @@ -528,7 +611,7 @@ describe('getSecondaryAction', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, statusNum: CONST.REPORT.STATUS_NUM.OPEN, stateNum: CONST.REPORT.STATE_NUM.OPEN, } as unknown as Report; @@ -551,7 +634,7 @@ describe('getSecondaryTransactionThreadActions', () => { jest.clearAllMocks(); Onyx.clear(); await Onyx.merge(ONYXKEYS.SESSION, SESSION); - await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {[CURRENT_USER_ACCOUNT_ID]: PERSONAL_DETAILS}); + await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {[EMPLOYEE_ACCOUNT_ID]: PERSONAL_DETAILS}); }); it('should always return VIEW_DETAILS', () => { @@ -565,7 +648,7 @@ describe('getSecondaryTransactionThreadActions', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, } as unknown as Report; @@ -582,7 +665,7 @@ describe('getSecondaryTransactionThreadActions', () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: EMPLOYEE_ACCOUNT_ID, statusNum: CONST.REPORT.STATUS_NUM.OPEN, stateNum: CONST.REPORT.STATE_NUM.OPEN, } as unknown as Report;