From 2953cb7073efc7c3552c1470d46e559eadaeeabd Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Dec 2023 01:59:39 +0800 Subject: [PATCH 01/10] disable request money on a submitted expense (paid policy) --- src/components/ReportWelcomeText.js | 2 +- src/libs/ReportUtils.ts | 16 ++++++++++------ .../AttachmentPickerWithMenuItems.js | 13 ++++++++++--- src/pages/iou/request/IOURequestStartPage.js | 6 +++++- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/ReportWelcomeText.js b/src/components/ReportWelcomeText.js index a204d0c59aaf..3693d02252f0 100644 --- a/src/components/ReportWelcomeText.js +++ b/src/components/ReportWelcomeText.js @@ -70,7 +70,7 @@ function ReportWelcomeText(props) { ); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(props.policy); const roomWelcomeMessage = ReportUtils.getRoomWelcomeMessage(props.report, isUserPolicyAdmin); - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(props.report, participantAccountIDs); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(props.report, props.policy, participantAccountIDs); return ( <> diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 29b32c35e6a4..b9fd06e22b11 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3763,7 +3763,7 @@ function hasIOUWaitingOnCurrentUserBankAccount(chatReport: OnyxEntry): b * - in an IOU report, which is not settled yet * - in a 1:1 DM chat */ -function canRequestMoney(report: OnyxEntry, otherParticipants: number[]): boolean { +function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, otherParticipants: number[]): boolean { // User cannot request money in chat thread or in task report or in chat room if (isChatThread(report) || isTaskReport(report) || isChatRoom(report)) { return false; @@ -3793,7 +3793,11 @@ function canRequestMoney(report: OnyxEntry, otherParticipants: number[]) // User can request money in any IOU report, unless paid, but user can only request money in an expense report // which is tied to their workspace chat. if (isMoneyRequestReport(report)) { - return ((isExpenseReport(report) && isOwnPolicyExpenseChat) || isIOUReport(report)) && !isReportApproved(report) && !isSettled(report?.reportID); + const isOwnExpenseReport = isExpenseReport(report) && isOwnPolicyExpenseChat; + if (isOwnExpenseReport && (policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE)) { + return isDraftExpenseReport(report); + } + return (isOwnExpenseReport || isIOUReport(report)) && !isReportApproved(report) && !isSettled(report?.reportID); } // In case of policy expense chat, users can only request money from their own policy expense chat @@ -3818,7 +3822,7 @@ function canRequestMoney(report: OnyxEntry, otherParticipants: number[]) * None of the options should show in chat threads or if there is some special Expensify account * as a participant of the report. */ -function getMoneyRequestOptions(report: OnyxEntry, reportParticipants: number[]): Array> { +function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, reportParticipants: number[]): Array> { // In any thread or task report, we do not allow any new money requests yet if (isChatThread(report) || isTaskReport(report)) { return []; @@ -3844,7 +3848,7 @@ function getMoneyRequestOptions(report: OnyxEntry, reportParticipants: n options = [CONST.IOU.TYPE.SPLIT]; } - if (canRequestMoney(report, otherParticipants)) { + if (canRequestMoney(report, policy, otherParticipants)) { options = [...options, CONST.IOU.TYPE.REQUEST]; } @@ -4001,12 +4005,12 @@ function getPolicyExpenseChatReportIDByOwner(policyOwner: string): string | null /** * Check if the report can create the request with type is iouType */ -function canCreateRequest(report: OnyxEntry, iouType: (typeof CONST.IOU.TYPE)[keyof typeof CONST.IOU.TYPE]): boolean { +function canCreateRequest(report: OnyxEntry, policy: OnyxEntry, iouType: (typeof CONST.IOU.TYPE)[keyof typeof CONST.IOU.TYPE]): boolean { const participantAccountIDs = report?.participantAccountIDs ?? []; if (!canUserPerformWriteAction(report)) { return false; } - return getMoneyRequestOptions(report, participantAccountIDs).includes(iouType); + return getMoneyRequestOptions(report, policy, participantAccountIDs).includes(iouType); } function getWorkspaceChats(policyID: string, accountIDs: number[]): Array> { diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index 1aeae46c252f..d9462ea8ff79 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React, {useMemo} from 'react'; import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import AttachmentPicker from '@components/AttachmentPicker'; import Icon from '@components/Icon'; @@ -18,6 +19,7 @@ import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Task from '@userActions/Task'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; const propTypes = { @@ -94,6 +96,7 @@ const defaultProps = { */ function AttachmentPickerWithMenuItems({ report, + policy, reportParticipantIDs, displayFileInModal, isFullComposerAvailable, @@ -138,10 +141,10 @@ function AttachmentPickerWithMenuItems({ }, }; - return _.map(ReportUtils.getMoneyRequestOptions(report, reportParticipantIDs), (option) => ({ + return _.map(ReportUtils.getMoneyRequestOptions(report, policy, reportParticipantIDs), (option) => ({ ...options[option], })); - }, [report, reportParticipantIDs, translate]); + }, [report, policy, reportParticipantIDs, translate]); /** * Determines if we can show the task option @@ -283,4 +286,8 @@ AttachmentPickerWithMenuItems.propTypes = propTypes; AttachmentPickerWithMenuItems.defaultProps = defaultProps; AttachmentPickerWithMenuItems.displayName = 'AttachmentPickerWithMenuItems'; -export default AttachmentPickerWithMenuItems; +export default withOnyx({ + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + }, +})(AttachmentPickerWithMenuItems); diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index 6572e154ee14..4848bdc7d6d9 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -52,6 +52,7 @@ const defaultProps = { function IOURequestStartPage({ report, + policy, route, route: { params: {iouType, reportID}, @@ -92,7 +93,7 @@ function IOURequestStartPage({ const shouldDisplayDistanceRequest = isExpenseChat || isExpenseReport || isFromGlobalCreate; // Allow the user to create the request if we are creating the request in global menu or the report can create the request - const isAllowedToCreateRequest = _.isEmpty(report.reportID) || ReportUtils.canCreateRequest(report, iouType); + const isAllowedToCreateRequest = _.isEmpty(report.reportID) || ReportUtils.canCreateRequest(report, policy, iouType); const navigateBack = () => { Navigation.dismissModal(); @@ -166,6 +167,9 @@ export default withOnyx({ report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`, }, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`, + }, selectedTab: { key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.IOU_REQUEST_TYPE}`, }, From eac52c220e70d66a1ea1f30ce8d679228acc92f8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Dec 2023 02:13:26 +0800 Subject: [PATCH 02/10] add policy --- tests/perf-test/ReportUtils.perf-test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index ab6ee72a0082..2b35a2168675 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -200,6 +200,7 @@ test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => { test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => { const report = {...createRandomReport(1), type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true}; + const policy = createRandomPolicy(1) const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1); await Onyx.multiSet({ @@ -207,7 +208,7 @@ test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => { }); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, reportParticipants), {runs}); + await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants), {runs}); }); test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => { From 5e6c1d102f9a0101ba7009b885addfd5a702758a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Dec 2023 02:24:46 +0800 Subject: [PATCH 03/10] fix test by passing an empty policy object --- tests/unit/ReportUtilsTest.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index 090140f20ecd..3291a01bef5a 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -339,7 +339,7 @@ describe('ReportUtils', () => { describe('return empty iou options if', () => { it('participants aray contains excluded expensify iou emails', () => { const allEmpty = _.every(CONST.EXPENSIFY_ACCOUNT_IDS, (accountID) => { - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions({}, [currentUserAccountID, accountID], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions({}, {}, [currentUserAccountID, accountID]); return moneyRequestOptions.length === 0; }); expect(allEmpty).toBe(true); @@ -350,7 +350,7 @@ describe('ReportUtils', () => { ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); @@ -360,7 +360,7 @@ describe('ReportUtils', () => { chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: false, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); @@ -370,7 +370,7 @@ describe('ReportUtils', () => { type: CONST.REPORT.TYPE.IOU, statusNum: CONST.REPORT.STATUS.REIMBURSED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); @@ -381,7 +381,7 @@ describe('ReportUtils', () => { stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS.APPROVED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); @@ -391,7 +391,7 @@ describe('ReportUtils', () => { type: CONST.REPORT.TYPE.EXPENSE, statusNum: CONST.REPORT.STATUS.REIMBURSED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); @@ -405,7 +405,7 @@ describe('ReportUtils', () => { parentReportID: '100', type: CONST.REPORT.TYPE.EXPENSE, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID], [CONST.BETAS.IOU_SEND]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); }); }); @@ -420,7 +420,7 @@ describe('ReportUtils', () => { ...LHNTestUtils.getFakeReport(), chatType, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, participantsAccountIDs[0]], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {},[currentUserAccountID, participantsAccountIDs[0]]); return moneyRequestOptions.length === 1 && moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT); }, ); @@ -432,7 +432,7 @@ describe('ReportUtils', () => { ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, ...participantsAccountIDs], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); }); @@ -442,7 +442,7 @@ describe('ReportUtils', () => { ...LHNTestUtils.getFakeReport(), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, ...participantsAccountIDs], [CONST.BETAS.IOU_SEND]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); }); @@ -453,7 +453,7 @@ describe('ReportUtils', () => { type: CONST.REPORT.TYPE.CHAT, participantsAccountIDs: [currentUserAccountID, ...participantsAccountIDs], }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, ...participantsAccountIDs], [CONST.BETAS.IOU_SEND]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); }); @@ -471,7 +471,7 @@ describe('ReportUtils', () => { parentReportID: '101', type: CONST.REPORT.TYPE.EXPENSE, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID], [CONST.BETAS.IOU_SEND]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); }); @@ -485,7 +485,7 @@ describe('ReportUtils', () => { stateNum: CONST.REPORT.STATE_NUM.PROCESSING, statusNum: CONST.REPORT.STATUS.SUBMITTED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, participantsAccountIDs[0]], []); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); }); @@ -498,7 +498,7 @@ describe('ReportUtils', () => { stateNum: CONST.REPORT.STATE_NUM.PROCESSING, statusNum: CONST.REPORT.STATUS.SUBMITTED, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, participantsAccountIDs[0]], [CONST.BETAS.IOU_SEND]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(1); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); }); @@ -511,7 +511,7 @@ describe('ReportUtils', () => { chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, ...participantsAccountIDs], [CONST.BETAS.IOU_SEND]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, ...participantsAccountIDs]); expect(moneyRequestOptions.length).toBe(2); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT)).toBe(true); @@ -522,7 +522,7 @@ describe('ReportUtils', () => { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.CHAT, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, [currentUserAccountID, participantsAccountIDs[0]], [CONST.BETAS.IOU_SEND]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(2); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.REQUEST)).toBe(true); expect(moneyRequestOptions.includes(CONST.IOU.TYPE.SEND)).toBe(true); From fd66555df18fb0fe774e89648d81a9b434e788dd Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Dec 2023 02:30:37 +0800 Subject: [PATCH 04/10] add prop types --- .../ReportActionCompose/AttachmentPickerWithMenuItems.js | 7 +++++++ src/pages/iou/request/IOURequestStartPage.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index d9462ea8ff79..f064a0c31058 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -32,6 +32,12 @@ const propTypes = { loading: PropTypes.bool, }).isRequired, + /** The policy tied to the report */ + policy: PropTypes.shape({ + /** Type of the policy */ + type: PropTypes.string, + }), + /** The personal details of everyone in the report */ reportParticipantIDs: PropTypes.arrayOf(PropTypes.number), @@ -86,6 +92,7 @@ const propTypes = { const defaultProps = { reportParticipantIDs: [], + policy: {}, }; /** diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index 4848bdc7d6d9..388dd358019e 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -37,6 +37,12 @@ const propTypes = { /** The report that holds the transaction */ report: reportPropTypes, + /** The policy tied to the report */ + policy: PropTypes.shape({ + /** Type of the policy */ + type: PropTypes.string, + }), + /** The tab to select by default (whatever the user visited last) */ selectedTab: PropTypes.oneOf(_.values(CONST.TAB_REQUEST)), @@ -46,6 +52,7 @@ const propTypes = { const defaultProps = { report: {}, + policy: {}, selectedTab: CONST.TAB_REQUEST.SCAN, transaction: {}, }; From 22cd7f5e0dbd583f4b541741d2e4fccc21955803 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Dec 2023 02:47:44 +0800 Subject: [PATCH 05/10] add new test --- tests/unit/ReportUtilsTest.js | 48 ++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index 3291a01bef5a..0b16789bb581 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -409,6 +409,27 @@ describe('ReportUtils', () => { expect(moneyRequestOptions.length).toBe(0); }); }); + + it("it is a non-open expense report tied to user's own paid policy expense chat", () => { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}101`, { + reportID: '101', + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + isOwnPolicyExpenseChat: true, + }).then(() => { + const report = { + ...LHNTestUtils.getFakeReport(), + type: CONST.REPORT.TYPE.EXPENSE, + stateNum: CONST.REPORT.STATE_NUM.PROCESSING, + statusNum: CONST.REPORT.STATUS.SUBMITTED, + parentReportID: '101', + } + const policy = { + type: CONST.POLICY.TYPE.TEAM, + }; + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, policy, [currentUserAccountID, participantsAccountIDs[0]]); + expect(moneyRequestOptions.length).toBe(0); + }); + }); }); describe('return only iou split option if', () => { @@ -461,14 +482,14 @@ describe('ReportUtils', () => { describe('return only money request option if', () => { it("it is an expense report tied to user's own policy expense chat", () => { - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}101`, { - reportID: '101', + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}102`, { + reportID: '102', chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true, }).then(() => { const report = { ...LHNTestUtils.getFakeReport(), - parentReportID: '101', + parentReportID: '102', type: CONST.REPORT.TYPE.EXPENSE, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); @@ -477,6 +498,27 @@ describe('ReportUtils', () => { }); }); + it("it is an open expense report tied to user's own paid policy expense chat", () => { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}103`, { + reportID: '103', + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + isOwnPolicyExpenseChat: true, + }).then(() => { + const report = { + ...LHNTestUtils.getFakeReport(), + type: CONST.REPORT.TYPE.EXPENSE, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS.OPEN, + parentReportID: '103', + } + const policy = { + type: CONST.POLICY.TYPE.TEAM, + }; + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, policy, [currentUserAccountID, participantsAccountIDs[0]], true); + expect(moneyRequestOptions.length).toBe(1); + }); + }); + it('it is an IOU report in submitted state', () => { const report = { ...LHNTestUtils.getFakeReport(), From fde44dc0408ae670be1075e1994110ea3323f990 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Dec 2023 02:51:36 +0800 Subject: [PATCH 06/10] rename duplicated var --- tests/unit/ReportUtilsTest.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index 0b16789bb581..f276d6cc1358 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -423,10 +423,10 @@ describe('ReportUtils', () => { statusNum: CONST.REPORT.STATUS.SUBMITTED, parentReportID: '101', } - const policy = { + const paidPolicy = { type: CONST.POLICY.TYPE.TEAM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, policy, [currentUserAccountID, participantsAccountIDs[0]]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, paidPolicy, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(0); }); }); @@ -511,10 +511,10 @@ describe('ReportUtils', () => { statusNum: CONST.REPORT.STATUS.OPEN, parentReportID: '103', } - const policy = { + const paidPolicy = { type: CONST.POLICY.TYPE.TEAM, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, policy, [currentUserAccountID, participantsAccountIDs[0]], true); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, paidPolicy, [currentUserAccountID, participantsAccountIDs[0]], true); expect(moneyRequestOptions.length).toBe(1); }); }); From 479b01834ef8ea5bb625fb503380f921edc7e29a Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 16 Dec 2023 14:43:05 +0800 Subject: [PATCH 07/10] prettier --- tests/perf-test/ReportUtils.perf-test.ts | 2 +- tests/unit/ReportUtilsTest.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index 2b35a2168675..e3aa2fcf57f2 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -200,7 +200,7 @@ test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => { test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => { const report = {...createRandomReport(1), type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true}; - const policy = createRandomPolicy(1) + const policy = createRandomPolicy(1); const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1); await Onyx.multiSet({ diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index f276d6cc1358..d700aa4724f1 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -422,7 +422,7 @@ describe('ReportUtils', () => { stateNum: CONST.REPORT.STATE_NUM.PROCESSING, statusNum: CONST.REPORT.STATUS.SUBMITTED, parentReportID: '101', - } + }; const paidPolicy = { type: CONST.POLICY.TYPE.TEAM, }; @@ -441,7 +441,7 @@ describe('ReportUtils', () => { ...LHNTestUtils.getFakeReport(), chatType, }; - const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {},[currentUserAccountID, participantsAccountIDs[0]]); + const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); return moneyRequestOptions.length === 1 && moneyRequestOptions.includes(CONST.IOU.TYPE.SPLIT); }, ); @@ -510,12 +510,12 @@ describe('ReportUtils', () => { stateNum: CONST.REPORT.STATE_NUM.OPEN, statusNum: CONST.REPORT.STATUS.OPEN, parentReportID: '103', - } + }; const paidPolicy = { type: CONST.POLICY.TYPE.TEAM, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, paidPolicy, [currentUserAccountID, participantsAccountIDs[0]], true); - expect(moneyRequestOptions.length).toBe(1); + expect(moneyRequestOptions.length).toBe(1); }); }); From bbcdf6b6471692cd484b6b1adf1a10b9e713b4c0 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 18 Dec 2023 22:44:15 +0800 Subject: [PATCH 08/10] create a new util func --- src/libs/PolicyUtils.ts | 5 +++++ src/libs/ReportUtils.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 347e5b68e960..020301c06a71 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -197,6 +197,10 @@ function isPendingDeletePolicy(policy: OnyxEntry): boolean { return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; } +function isPaidGroupPolicy(policy: OnyxEntry): boolean { + return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE; +} + export { getActivePolicies, hasPolicyMemberError, @@ -217,4 +221,5 @@ export { getTagList, isPendingDeletePolicy, isPolicyMember, + isPaidGroupPolicy, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a85a8e23f873..4d1296a8ac51 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3794,7 +3794,7 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o // which is tied to their workspace chat. if (isMoneyRequestReport(report)) { const isOwnExpenseReport = isExpenseReport(report) && isOwnPolicyExpenseChat; - if (isOwnExpenseReport && (policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE)) { + if (isOwnExpenseReport && PolicyUtils.isPaidGroupPolicy(policy)) { return isDraftExpenseReport(report); } return (isOwnExpenseReport || isIOUReport(report)) && !isReportApproved(report) && !isSettled(report?.reportID); From 486abe80e4ddc119a271fea633ba070278bc155b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 18 Dec 2023 23:40:58 +0800 Subject: [PATCH 09/10] pass policy prop --- src/pages/iou/MoneyRequestSelectorPage.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index e367709cb010..7b87b50bb7f3 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -43,6 +43,12 @@ const propTypes = { /** Report on which the money request is being created */ report: reportPropTypes, + /** The policy tied to the report */ + policy: PropTypes.shape({ + /** Type of the policy */ + type: PropTypes.string, + }), + /** Which tab has been selected */ selectedTab: PropTypes.string, }; @@ -50,6 +56,7 @@ const propTypes = { const defaultProps = { selectedTab: CONST.TAB_REQUEST.SCAN, report: {}, + policy: {}, }; function MoneyRequestSelectorPage(props) { @@ -76,7 +83,7 @@ function MoneyRequestSelectorPage(props) { }; // Allow the user to create the request if we are creating the request in global menu or the report can create the request - const isAllowedToCreateRequest = _.isEmpty(props.report.reportID) || ReportUtils.canCreateRequest(props.report, iouType); + const isAllowedToCreateRequest = _.isEmpty(props.report.reportID) || ReportUtils.canCreateRequest(props.report, props.policy, iouType); const prevSelectedTab = usePrevious(props.selectedTab); useEffect(() => { @@ -159,5 +166,8 @@ export default compose( selectedTab: { key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, }, + policy: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`, + }, }), )(MoneyRequestSelectorPage); From d0e8f346ed35f2f0a701f01817572aa678de019d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 26 Dec 2023 20:02:54 +0800 Subject: [PATCH 10/10] safely access policy id --- .../ReportActionCompose/AttachmentPickerWithMenuItems.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js index a0d85a06def6..1bfbc0125221 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js @@ -1,3 +1,4 @@ +import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; @@ -336,7 +337,7 @@ export default compose( withNavigationFocus, withOnyx({ policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`, }, }), )(AttachmentPickerWithMenuItems);