Skip to content

Commit

Permalink
Merge pull request #33182 from bernhardoj/fix/32796-disable-request-o…
Browse files Browse the repository at this point in the history
…n-submitted-expense

Disable request money on a paid policy submitted expense
  • Loading branch information
mountiny authored Dec 26, 2023
2 parents 7a59984 + d0e8f34 commit e21cbb5
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/components/ReportWelcomeText.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
Expand Down
5 changes: 5 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ function isPendingDeletePolicy(policy: OnyxEntry<Policy>): boolean {
return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
}

function isPaidGroupPolicy(policy: OnyxEntry<Policy>): boolean {
return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE;
}

export {
getActivePolicies,
hasPolicyMemberError,
Expand All @@ -217,4 +221,5 @@ export {
getTagList,
isPendingDeletePolicy,
isPolicyMember,
isPaidGroupPolicy,
};
16 changes: 10 additions & 6 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3634,7 +3634,7 @@ function hasIOUWaitingOnCurrentUserBankAccount(chatReport: OnyxEntry<Report>): b
* - in an IOU report, which is not settled yet
* - in a 1:1 DM chat
*/
function canRequestMoney(report: OnyxEntry<Report>, otherParticipants: number[]): boolean {
function canRequestMoney(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>, 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;
Expand Down Expand Up @@ -3664,7 +3664,11 @@ function canRequestMoney(report: OnyxEntry<Report>, 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 && PolicyUtils.isPaidGroupPolicy(policy)) {
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
Expand All @@ -3689,7 +3693,7 @@ function canRequestMoney(report: OnyxEntry<Report>, 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<Report>, reportParticipants: number[]): Array<ValueOf<typeof CONST.IOU.TYPE>> {
function getMoneyRequestOptions(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>, reportParticipants: number[]): Array<ValueOf<typeof CONST.IOU.TYPE>> {
// In any thread or task report, we do not allow any new money requests yet
if (isChatThread(report) || isTaskReport(report)) {
return [];
Expand All @@ -3715,7 +3719,7 @@ function getMoneyRequestOptions(report: OnyxEntry<Report>, reportParticipants: n
options = [CONST.IOU.TYPE.SPLIT];
}

if (canRequestMoney(report, otherParticipants)) {
if (canRequestMoney(report, policy, otherParticipants)) {
options = [...options, CONST.IOU.TYPE.REQUEST];
}

Expand Down Expand Up @@ -3872,12 +3876,12 @@ function getPolicyExpenseChatReportIDByOwner(policyOwner: string): string | null
/**
* Check if the report can create the request with type is iouType
*/
function canCreateRequest(report: OnyxEntry<Report>, iouType: (typeof CONST.IOU.TYPE)[keyof typeof CONST.IOU.TYPE]): boolean {
function canCreateRequest(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>, 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<OnyxEntry<Report>> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, 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';
Expand All @@ -15,12 +17,14 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
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 = {
Expand All @@ -33,6 +37,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),

Expand Down Expand Up @@ -90,6 +100,7 @@ const propTypes = {

const defaultProps = {
reportParticipantIDs: [],
policy: {},
};

/**
Expand All @@ -100,6 +111,7 @@ const defaultProps = {
*/
function AttachmentPickerWithMenuItems({
report,
policy,
reportParticipantIDs,
displayFileInModal,
isFullComposerAvailable,
Expand Down Expand Up @@ -146,10 +158,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
Expand Down Expand Up @@ -321,4 +333,11 @@ AttachmentPickerWithMenuItems.propTypes = propTypes;
AttachmentPickerWithMenuItems.defaultProps = defaultProps;
AttachmentPickerWithMenuItems.displayName = 'AttachmentPickerWithMenuItems';

export default withNavigationFocus(AttachmentPickerWithMenuItems);
export default compose(
withNavigationFocus,
withOnyx({
policy: {
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`,
},
}),
)(AttachmentPickerWithMenuItems);
12 changes: 11 additions & 1 deletion src/pages/iou/MoneyRequestSelectorPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,20 @@ 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,
};

const defaultProps = {
selectedTab: CONST.TAB_REQUEST.SCAN,
report: {},
policy: {},
};

function MoneyRequestSelectorPage(props) {
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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);
13 changes: 12 additions & 1 deletion src/pages/iou/request/IOURequestStartPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)),

Expand All @@ -46,12 +52,14 @@ const propTypes = {

const defaultProps = {
report: {},
policy: {},
selectedTab: CONST.TAB_REQUEST.SCAN,
transaction: {},
};

function IOURequestStartPage({
report,
policy,
route,
route: {
params: {iouType, reportID},
Expand Down Expand Up @@ -92,7 +100,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();
Expand Down Expand Up @@ -166,6 +174,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}`,
},
Expand Down
3 changes: 2 additions & 1 deletion tests/perf-test/ReportUtils.perf-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,15 @@ 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({
...mockedPoliciesMap,
});

await waitForBatchedUpdates();
await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, reportParticipants), {runs});
await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants), {runs});
});

test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => {
Expand Down
Loading

0 comments on commit e21cbb5

Please sign in to comment.