diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index ab95fb749ac1..19f4a5b8e103 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -106,6 +106,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should const isExpensifyCardTransaction = TransactionUtils.isExpensifyCardTransaction(transaction); const cardProgramName = isExpensifyCardTransaction ? CardUtils.getCardDescription(transactionCardID) : ''; + // Flags for allowing or disallowing editing a money request const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction) && !isExpensifyCardTransaction; @@ -181,8 +182,8 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should titleIcon={Expensicons.Checkmark} description={amountDescription} titleStyle={styles.newKansasLarge} - interactive={canEdit} - shouldShowRightIcon={canEdit} + interactive={canEdit && !isSettled} + shouldShowRightIcon={canEdit && !isSettled} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} brickRoadIndicator={hasErrors && transactionAmount === 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''} @@ -206,8 +207,8 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))} /> @@ -230,8 +231,8 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} brickRoadIndicator={hasErrors && transactionDate === '' ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index dccebaf3151b..b974c1cef700 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1487,28 +1487,70 @@ function getTransactionDetails(transaction, createdDateFormat = CONST.DATE.FNS_F * Can only edit if: * * - in case of IOU report - * - the current user is the requestor + * - the current user is the requestor and is not settled yet * - in case of expense report - * - the current user is the requestor + * - the current user is the requestor and is not settled yet * - or the user is an admin on the policy the expense report is tied to * * @param {Object} reportAction * @returns {Boolean} */ function canEditMoneyRequest(reportAction) { - // If the report action i snot IOU type, return true early + const isDeleted = ReportActionsUtils.isDeletedAction(reportAction); + + if (isDeleted) { + return false; + } + + // If the report action is not IOU type, return true early if (reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return true; } + const moneyRequestReportID = lodashGet(reportAction, 'originalMessage.IOUReportID', 0); + if (!moneyRequestReportID) { return false; } + const moneyRequestReport = getReport(moneyRequestReportID); const isReportSettled = isSettled(moneyRequestReport.reportID); const isAdmin = isExpenseReport(moneyRequestReport) && lodashGet(getPolicy(moneyRequestReport.policyID), 'role', '') === CONST.POLICY.ROLE.ADMIN; const isRequestor = currentUserAccountID === reportAction.actorAccountID; - return !isReportSettled && (isAdmin || isRequestor); + + if (isAdmin) { + return true; + } + + return !isReportSettled && isRequestor; +} + +/** + * Checks if the current user can edit the provided property of a money request + * + * @param {Object} reportAction + * @param {String} reportID + * @param {String} fieldToEdit + * @returns {Boolean} + */ +function canEditFieldOfMoneyRequest(reportAction, reportID, fieldToEdit) { + // A list of fields that cannot be edited by anyone, once a money request has been settled + const nonEditableFieldsWhenSettled = [ + CONST.EDIT_REQUEST_FIELD.AMOUNT, + CONST.EDIT_REQUEST_FIELD.CURRENCY, + CONST.EDIT_REQUEST_FIELD.DATE, + CONST.EDIT_REQUEST_FIELD.RECEIPT, + CONST.EDIT_REQUEST_FIELD.DISTANCE, + ]; + + // Checks if this user has permissions to edit this money request + if (!canEditMoneyRequest(reportAction)) { + return false; // User doesn't have permission to edit + } + + // Checks if the report is settled + // Checks if the provided property is a restricted one + return !isSettled(reportID) || !nonEditableFieldsWhenSettled.includes(fieldToEdit); } /** @@ -4124,6 +4166,7 @@ export { getTaskAssigneeChatOnyxData, getParticipantsIDs, canEditMoneyRequest, + canEditFieldOfMoneyRequest, buildTransactionThread, areAllRequestsBeingSmartScanned, getTransactionsWithReceipts, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index a85f490bbb42..f039afeb085f 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -8,16 +8,13 @@ import ONYXKEYS from '../ONYXKEYS'; import ROUTES from '../ROUTES'; import compose from '../libs/compose'; import Navigation from '../libs/Navigation/Navigation'; -import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import * as ReportUtils from '../libs/ReportUtils'; import * as PolicyUtils from '../libs/PolicyUtils'; import * as TransactionUtils from '../libs/TransactionUtils'; -import * as Policy from '../libs/actions/Policy'; import * as IOU from '../libs/actions/IOU'; import * as CurrencyUtils from '../libs/CurrencyUtils'; import * as OptionsListUtils from '../libs/OptionsListUtils'; import Permissions from '../libs/Permissions'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../components/withCurrentUserPersonalDetails'; import tagPropTypes from '../components/tagPropTypes'; import FullPageNotFoundView from '../components/BlockingViews/FullPageNotFoundView'; import EditRequestDescriptionPage from './EditRequestDescriptionPage'; @@ -31,6 +28,7 @@ import EditRequestCategoryPage from './EditRequestCategoryPage'; import EditRequestTagPage from './EditRequestTagPage'; import categoryPropTypes from '../components/categoryPropTypes'; import ScreenWrapper from '../components/ScreenWrapper'; +import reportActionPropTypes from './home/report/reportActionPropTypes'; import transactionPropTypes from '../components/transactionPropTypes'; const propTypes = { @@ -56,49 +54,32 @@ const propTypes = { /** The parent report object for the thread report */ parentReport: reportPropTypes, - /** The policy object for the current route */ - policy: PropTypes.shape({ - /** The name of the policy */ - name: PropTypes.string, - - /** The URL for the policy avatar */ - avatar: PropTypes.string, - }), - - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user email */ - email: PropTypes.string, - }), - /** Collection of categories attached to a policy */ policyCategories: PropTypes.objectOf(categoryPropTypes), /** Collection of tags attached to a policy */ policyTags: tagPropTypes, - /** The original transaction that is being edited */ - transaction: transactionPropTypes, + /** The actions from the parent report */ + parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), - ...withCurrentUserPersonalDetailsPropTypes, + /** Transaction that stores the request data */ + transaction: transactionPropTypes, }; const defaultProps = { betas: [], report: {}, parentReport: {}, - policy: null, - session: { - email: null, - }, policyCategories: {}, policyTags: {}, + parentReportActions: {}, transaction: {}, }; -function EditRequestPage({betas, report, route, parentReport, policy, session, policyCategories, policyTags, parentReportActions, transaction}) { +function EditRequestPage({betas, report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction}) { const parentReportActionID = lodashGet(report, 'parentReportActionID', '0'); - const parentReportAction = lodashGet(parentReportActions, parentReportActionID); + const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {}); const { amount: transactionAmount, currency: transactionCurrency, @@ -114,13 +95,6 @@ function EditRequestPage({betas, report, route, parentReport, policy, session, p const transactionCreated = TransactionUtils.getCreated(transaction); const fieldToEdit = lodashGet(route, ['params', 'field'], ''); - const isDeleted = ReportActionsUtils.isDeletedAction(parentReportAction); - const isSettled = ReportUtils.isSettled(parentReport.reportID); - - const isAdmin = Policy.isAdminOfFreePolicy([policy]) && ReportUtils.isExpenseReport(parentReport); - const isRequestor = ReportUtils.isMoneyRequestReport(parentReport) && lodashGet(session, 'accountID', null) === parentReportAction.actorAccountID; - const canEdit = !isSettled && !isDeleted && (isAdmin || isRequestor); - // For now, it always defaults to the first tag of the policy const policyTag = PolicyUtils.getTag(policyTags); const policyTagList = lodashGet(policyTag, 'tags', {}); @@ -135,15 +109,18 @@ function EditRequestPage({betas, report, route, parentReport, policy, session, p // A flag for showing the tags page const shouldShowTags = isPolicyExpenseChat && Permissions.canUseTags(betas) && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagList))); - // Dismiss the modal when the request is paid or deleted + // Decides whether to allow or disallow editing a money request useEffect(() => { - if (canEdit) { + // Do not dismiss the modal, when a current user can edit this property of the money request. + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, parentReport.reportID, fieldToEdit)) { return; } + + // Dismiss the modal when a current user cannot edit a money request. Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [canEdit]); + }, [parentReportAction, parentReport.reportID, fieldToEdit]); // Update the transaction object and close the modal function editMoneyRequest(transactionChanges) { @@ -300,7 +277,6 @@ EditRequestPage.displayName = 'EditRequestPage'; EditRequestPage.propTypes = propTypes; EditRequestPage.defaultProps = defaultProps; export default compose( - withCurrentUserPersonalDetails, withOnyx({ betas: { key: ONYXKEYS.BETAS, @@ -311,9 +287,6 @@ export default compose( }), // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file withOnyx({ - policy: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`, - }, policyCategories: { key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`, }, diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js index 038c0ac33606..1f52f85e83a3 100644 --- a/src/pages/iou/IOUCurrencySelection.js +++ b/src/pages/iou/IOUCurrencySelection.js @@ -1,4 +1,4 @@ -import React, {useState, useMemo, useCallback, useRef} from 'react'; +import React, {useState, useMemo, useCallback, useRef, useEffect} from 'react'; import {Keyboard} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; @@ -14,6 +14,8 @@ import compose from '../../libs/compose'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import {withNetwork} from '../../components/OnyxProvider'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; +import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; +import * as ReportUtils from '../../libs/ReportUtils'; import ROUTES from '../../ROUTES'; import {iouPropTypes, iouDefaultProps} from './propTypes'; import SelectionList from '../../components/SelectionList'; @@ -71,6 +73,28 @@ function IOUCurrencySelection(props) { const selectedCurrencyCode = (lodashGet(props.route, 'params.currency', props.iou.currency) || CONST.CURRENCY.USD).toUpperCase(); const iouType = lodashGet(props.route, 'params.iouType', CONST.IOU.TYPE.REQUEST); const reportID = lodashGet(props.route, 'params.reportID', ''); + const threadReportID = lodashGet(props.route, 'params.threadReportID', ''); + + // Decides whether to allow or disallow editing a money request + useEffect(() => { + // Do not dismiss the modal, when it is not the edit flow. + if (!threadReportID) { + return; + } + + const report = ReportUtils.getReport(threadReportID); + const parentReportAction = ReportActionsUtils.getReportAction(report.parentReportID, report.parentReportActionID); + + // Do not dismiss the modal, when a current user can edit this currency of this money request. + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, report.parentReportID, CONST.EDIT_REQUEST_FIELD.CURRENCY)) { + return; + } + + // Dismiss the modal when a current user cannot edit a money request. + Navigation.isNavigationReady().then(() => { + Navigation.dismissModal(); + }); + }, [threadReportID]); const confirmCurrencySelection = useCallback( (option) => {