Skip to content

Commit 06ea630

Browse files
authored
Merge pull request Expensify#63011 from nkdengineer/fix/61982
fix: IOU report cannot be moved to workspace
2 parents ce12189 + 1b40357 commit 06ea630

3 files changed

Lines changed: 50 additions & 44 deletions

File tree

src/libs/ReportUtils.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3775,6 +3775,27 @@ function getTransactionCommentObject(transaction: OnyxEntry<Transaction>): Comme
37753775
};
37763776
}
37773777

3778+
function isWorkspacePayer(memberLogin: string, policy: OnyxEntry<Policy>): boolean {
3779+
const isAdmin = policy?.employeeList?.[memberLogin]?.role === CONST.POLICY.ROLE.ADMIN;
3780+
if (isPaidGroupPolicyPolicyUtils(policy)) {
3781+
if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES) {
3782+
// If we get here without a reimburser only admin is the payer.
3783+
if (!policy?.achAccount?.reimburser) {
3784+
return isAdmin;
3785+
}
3786+
3787+
// If we are the reimburser then we are the payer.
3788+
const isReimburser = memberLogin === policy?.achAccount?.reimburser;
3789+
return isReimburser;
3790+
}
3791+
if (policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL) {
3792+
return isAdmin;
3793+
}
3794+
return false;
3795+
}
3796+
return false;
3797+
}
3798+
37783799
/**
37793800
* Can only edit if:
37803801
*
@@ -10489,11 +10510,27 @@ function hasExportError(reportActions: OnyxEntry<ReportActions> | ReportAction[]
1048910510
return Object.values(reportActions).some((action) => isIntegrationMessageAction(action));
1049010511
}
1049110512

10513+
function doesReportContainRequestsFromMultipleUsers(iouReport: OnyxEntry<Report>): boolean {
10514+
const transactions = getReportTransactions(iouReport?.reportID);
10515+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
10516+
return isIOUReport(iouReport) && transactions.some((transaction) => (transaction?.modifiedAmount || transaction?.amount) < 0);
10517+
}
10518+
1049210519
/**
1049310520
* Determines whether the report can be moved to the workspace.
1049410521
*/
1049510522
function isWorkspaceEligibleForReportChange(newPolicy: OnyxEntry<Policy>, report: OnyxEntry<Report>, policies: OnyxCollection<Policy>): boolean {
1049610523
const submitterEmail = getLoginByAccountID(report?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID);
10524+
const managerLogin = getLoginByAccountID(report?.managerID ?? CONST.DEFAULT_NUMBER_ID);
10525+
// We can't move the iou report to the workspace if both users from the iou report create the expense
10526+
if (doesReportContainRequestsFromMultipleUsers(report)) {
10527+
return false;
10528+
}
10529+
10530+
// We can only move the iou report to the workspace if the manager is the payer of the new policy
10531+
if (isIOUReport(report)) {
10532+
return isPaidGroupPolicyPolicyUtils(newPolicy) && isWorkspacePayer(managerLogin ?? '', newPolicy);
10533+
}
1049710534
return isPaidGroupPolicyPolicyUtils(newPolicy) && (isPolicyMember(submitterEmail, newPolicy?.id) || isPolicyAdmin(newPolicy?.id, policies));
1049810535
}
1049910536

tests/unit/PolicyUtilsTest.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -668,14 +668,15 @@ describe('PolicyUtils', () => {
668668
expect(result).toBe(false);
669669
});
670670

671-
it('returns true if policy is paid group policy and submitter is a member of the new policy', async () => {
671+
it('returns true if policy is paid group policy and the manger is the payer', async () => {
672672
const currentUserLogin = employeeEmail;
673673
const currentUserAccountID = employeeAccountID;
674674

675675
const newPolicy = {
676676
...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM),
677+
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL,
677678
employeeList: {
678-
[currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.USER},
679+
[currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.ADMIN},
679680
},
680681
};
681682
const policies = {[`${ONYXKEYS.COLLECTION.POLICY}${newPolicy.id}`]: newPolicy};
@@ -684,22 +685,20 @@ describe('PolicyUtils', () => {
684685
...createRandomReport(0),
685686
type: CONST.REPORT.TYPE.IOU,
686687
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
687-
ownerAccountID: currentUserAccountID,
688-
managerID: approverAccountID,
688+
ownerAccountID: approverAccountID,
689+
managerID: currentUserAccountID,
689690
};
690691

691692
const result = isWorkspaceEligibleForReportChange(newPolicy, report, policies);
692693
expect(result).toBe(true);
693694
});
694695

695-
it('returns true if policy is paid group policy and submitter is not a member but current user is admin', async () => {
696-
const currentUserLogin = adminEmail;
697-
696+
it('returns false if the manager is not the payer of the new policy', async () => {
698697
const newPolicy = {
699698
...createRandomPolicy(1, CONST.POLICY.TYPE.TEAM),
700699
role: CONST.POLICY.ROLE.ADMIN,
701700
employeeList: {
702-
[currentUserLogin]: {email: currentUserLogin, role: CONST.POLICY.ROLE.ADMIN},
701+
[approverEmail]: {email: approverEmail, role: CONST.POLICY.ROLE.USER},
703702
},
704703
};
705704
const policies = {[`${ONYXKEYS.COLLECTION.POLICY}${newPolicy.id}`]: newPolicy};
@@ -713,7 +712,7 @@ describe('PolicyUtils', () => {
713712
};
714713

715714
const result = isWorkspaceEligibleForReportChange(newPolicy, report, policies);
716-
expect(result).toBe(true);
715+
expect(result).toBe(false);
717716
});
718717
});
719718

tests/unit/ReportSecondaryActionUtilsTest.ts

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,12 @@ describe('getSecondaryAction', () => {
449449
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.HOLD)).toBe(true);
450450
});
451451

452-
it('includes CHANGE_WORKSPACE option for submitted IOU report and submitter being a member of the new policy', async () => {
452+
it('includes CHANGE_WORKSPACE option for submitted IOU report and manager being the payer of the new policy', async () => {
453453
const report = {
454454
reportID: REPORT_ID,
455455
type: CONST.REPORT.TYPE.IOU,
456-
ownerAccountID: EMPLOYEE_ACCOUNT_ID,
457-
managerID: MANAGER_ACCOUNT_ID,
456+
ownerAccountID: MANAGER_ACCOUNT_ID,
457+
managerID: EMPLOYEE_ACCOUNT_ID,
458458
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
459459
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
460460
} as unknown as Report;
@@ -465,8 +465,9 @@ describe('getSecondaryAction', () => {
465465
const policy = {
466466
id: POLICY_ID,
467467
type: CONST.POLICY.TYPE.TEAM,
468+
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_MANUAL,
468469
employeeList: {
469-
[EMPLOYEE_EMAIL]: {email: EMPLOYEE_EMAIL, role: CONST.POLICY.ROLE.USER},
470+
[EMPLOYEE_EMAIL]: {email: EMPLOYEE_EMAIL, role: CONST.POLICY.ROLE.ADMIN},
470471
[MANAGER_EMAIL]: {email: MANAGER_EMAIL, role: CONST.POLICY.ROLE.USER},
471472
},
472473
} as unknown as Policy;
@@ -480,37 +481,6 @@ describe('getSecondaryAction', () => {
480481
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true);
481482
});
482483

483-
it('includes CHANGE_WORKSPACE option for IOU and the submitter is not a member but current user is admin', async () => {
484-
const report = {
485-
reportID: REPORT_ID,
486-
type: CONST.REPORT.TYPE.IOU,
487-
ownerAccountID: EMPLOYEE_ACCOUNT_ID,
488-
managerID: MANAGER_ACCOUNT_ID,
489-
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
490-
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
491-
} as unknown as Report;
492-
const personalDetails = {
493-
[ADMIN_ACCOUNT_ID]: {login: ADMIN_EMAIL},
494-
[MANAGER_ACCOUNT_ID]: {login: MANAGER_EMAIL},
495-
};
496-
const policy = {
497-
id: POLICY_ID,
498-
type: CONST.POLICY.TYPE.TEAM,
499-
role: CONST.POLICY.ROLE.ADMIN,
500-
employeeList: {
501-
[ADMIN_EMAIL]: {email: ADMIN_EMAIL, role: CONST.POLICY.ROLE.ADMIN},
502-
},
503-
} as unknown as Policy;
504-
const policies = {[`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`]: policy};
505-
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy);
506-
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);
507-
await Onyx.merge(ONYXKEYS.SESSION, {email: EMPLOYEE_EMAIL, accountID: EMPLOYEE_ACCOUNT_ID});
508-
await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails);
509-
510-
const result = getSecondaryReportActions(report, [], {}, policy, undefined, undefined, policies, true);
511-
expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE)).toBe(true);
512-
});
513-
514484
it('includes CHANGE_WORKSPACE option for open expense report submitter', async () => {
515485
const report = {
516486
reportID: REPORT_ID,

0 commit comments

Comments
 (0)