diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 4c7bd54efa18..2c51d6332946 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -33,21 +33,25 @@ const propTypes = { // eslint-disable-next-line react/forbid-prop-types fullReport: PropTypes.object, - /** The policies which the user has access to and which the report could be tied to */ - policies: PropTypes.objectOf( - PropTypes.shape({ - /** The ID of the policy */ - id: PropTypes.string, - /** Name of the policy */ - name: PropTypes.string, - /** Avatar of the policy */ - avatar: PropTypes.string, - }), - ), + /** The policy which the user has access to and which the report could be tied to */ + policy: PropTypes.shape({ + /** The ID of the policy */ + id: PropTypes.string, + /** Name of the policy */ + name: PropTypes.string, + /** Avatar of the policy */ + avatar: PropTypes.string, + }), /** The actions from the parent report */ parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + /** The transaction from the parent report action */ + transaction: PropTypes.shape({ + /** The ID of the transaction */ + transactionID: PropTypes.string, + }), + ...withCurrentReportIDPropTypes, ...basePropTypes, }; @@ -56,8 +60,9 @@ const defaultProps = { shouldDisableFocusOptions: false, personalDetails: {}, fullReport: {}, - policies: {}, + policy: {}, parentReportActions: {}, + transaction: {}, preferredLocale: CONST.LOCALES.DEFAULT, ...withCurrentReportIDDefaultProps, ...baseDefaultProps, @@ -77,9 +82,10 @@ function OptionRowLHNData({ personalDetails, preferredLocale, comment, - policies, + policy, receiptTransactions, parentReportActions, + transaction, ...propsToForward }) { const reportID = propsToForward.reportID; @@ -87,8 +93,6 @@ function OptionRowLHNData({ // instead of a changing number (so we prevent unnecessary re-renders). const isFocused = !shouldDisableFocusOptions && currentReportID === reportID; - const policy = lodashGet(policies, [`${ONYXKEYS.COLLECTION.POLICY}${fullReport.policyID}`], ''); - const parentReportAction = parentReportActions[fullReport.parentReportActionID]; const optionItemRef = useRef(); @@ -109,8 +113,9 @@ function OptionRowLHNData({ optionItemRef.current = item; return item; // Listen parentReportAction to update title of thread report when parentReportAction changed + // Listen to transaction to update title of transaction report when transaction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction]); + }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { @@ -189,20 +194,26 @@ export default React.memo( preferredLocale: { key: ONYXKEYS.NVP_PREFERRED_LOCALE, }, - policies: { - key: ONYXKEYS.COLLECTION.POLICY, - }, }), withOnyx({ parentReportActions: { key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`, canEvict: false, }, + policy: { + key: ({fullReport}) => `${ONYXKEYS.COLLECTION.POLICY}${fullReport.policyID}`, + }, // Ideally, we aim to access only the last transaction for the current report by listening to changes in reportActions. // In some scenarios, a transaction might be created after reportActions have been modified. // This can lead to situations where `lastTransaction` doesn't update and retains the previous value. // However, performance overhead of this is minimized by using memos inside the component. receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, }), + withOnyx({ + transaction: { + key: ({fullReport, parentReportActions}) => + `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportActions, [fullReport.parentReportActionID, 'originalMessage', 'IOUTransactionID'], '')}`, + }, + }), )(OptionRowLHNData), ); diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index f04a41ae1153..bdd7365b7893 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -15,19 +15,16 @@ import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import ONYXKEYS from '../ONYXKEYS'; import * as IOU from '../libs/actions/IOU'; -import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import ConfirmModal from './ConfirmModal'; import useLocalize from '../hooks/useLocalize'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import * as TransactionUtils from '../libs/TransactionUtils'; +import reportActionPropTypes from '../pages/home/report/reportActionPropTypes'; const propTypes = { /** The report currently being looked at */ report: iouReportPropTypes.isRequired, - /** The expense report or iou report (only will have a value if this is a transaction thread) */ - parentReport: iouReportPropTypes, - /** The policy which the report is tied to */ policy: PropTypes.shape({ /** Name of the policy */ @@ -37,12 +34,25 @@ const propTypes = { /** Personal details so we can get the ones for the report participants */ personalDetails: PropTypes.objectOf(participantPropTypes).isRequired, + /** Onyx Props */ /** Session info for the currently logged in user. */ session: PropTypes.shape({ /** Currently logged in user email */ email: PropTypes.string, }), + /** The expense report or iou report (only will have a value if this is a transaction thread) */ + parentReport: iouReportPropTypes, + + /** The report action the transaction is tied to from the parent report */ + parentReportAction: PropTypes.shape(reportActionPropTypes), + + /** The transaction from the parent report action */ + transaction: PropTypes.shape({ + /** The ID of the transaction */ + transactionID: PropTypes.string, + }), + ...windowDimensionsPropTypes, }; @@ -51,6 +61,8 @@ const defaultProps = { email: null, }, parentReport: {}, + parentReportAction: {}, + transaction: {}, }; function MoneyRequestHeader(props) { @@ -59,21 +71,18 @@ function MoneyRequestHeader(props) { const moneyRequestReport = props.parentReport; const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); - const parentReportAction = ReportActionsUtils.getParentReportAction(props.report); - // Only the requestor can take delete the request, admins can only edit it. - const isActionOwner = parentReportAction.actorAccountID === lodashGet(props.session, 'accountID', null); + const isActionOwner = props.parentReportAction.actorAccountID === lodashGet(props.session, 'accountID', null); const report = props.report; report.ownerAccountID = lodashGet(props, ['parentReport', 'ownerAccountID'], null); report.ownerEmail = lodashGet(props, ['parentReport', 'ownerEmail'], ''); const deleteTransaction = useCallback(() => { - IOU.deleteMoneyRequest(parentReportAction.originalMessage.IOUTransactionID, parentReportAction, true); + IOU.deleteMoneyRequest(props.parentReportAction.originalMessage.IOUTransactionID, props.parentReportAction, true); setIsDeleteModalVisible(false); - }, [parentReportAction, setIsDeleteModalVisible]); + }, [props.parentReportAction, setIsDeleteModalVisible]); - const transaction = TransactionUtils.getLinkedTransaction(parentReportAction); - const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + const isScanning = TransactionUtils.hasReceipt(props.transaction) && TransactionUtils.isReceiptBeingScanned(props.transaction); return ( <> @@ -85,7 +94,7 @@ function MoneyRequestHeader(props) { threeDotsMenuItems={[ { icon: Expensicons.Trashcan, - text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), + text: translate('reportActionContextMenu.deleteAction', {action: props.parentReportAction}), onSelected: () => setIsDeleteModalVisible(true), }, ]} @@ -125,5 +134,14 @@ export default compose( parentReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, }, + parentReportAction: { + key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${(report.parentReportID, report.parentReportActionID)}`, + selector: (reportActions, props) => props && props.parentReport && reportActions && reportActions[props.parentReport.parentReportActionID], + canEvict: false, + }, + transaction: { + key: ({report, parentReportActions}) => + `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportActions, [report.parentReportActionID, 'originalMessage', 'IOUTransactionID'], '')}`, + }, }), )(MoneyRequestHeader); diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index c677fe8f6c28..8f18119203be 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1033,12 +1033,50 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC const transactionThread = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`]; const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread.parentReportID}`]; + const chatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`]; const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); // STEP 2: Build new modified expense report action. const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport); const updatedTransaction = TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport); + // STEP 3: Compute the IOU total and update the report preview message so LHN amount owed is correct + // Should only update if the transaction matches the currency of the report, else we wait for the update + // from the server with the currency conversion + let updatedMoneyRequestReport = {...iouReport}; + const updatedChatReport = {...chatReport}; + if (updatedTransaction.currency === iouReport.currency && updatedTransaction.modifiedAmount) { + const diff = TransactionUtils.getAmount(transaction, true) - TransactionUtils.getAmount(updatedTransaction, true); + if (ReportUtils.isExpenseReport(iouReport)) { + updatedMoneyRequestReport.total += diff; + } else { + updatedMoneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID, diff, TransactionUtils.getCurrency(transaction), false); + } + + updatedMoneyRequestReport.cachedTotal = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedTransaction.currency); + + // Update the last message of the IOU report + const lastMessage = ReportUtils.getIOUReportActionMessage( + iouReport.reportID, + CONST.IOU.REPORT_ACTION_TYPE.CREATE, + updatedMoneyRequestReport.total, + '', + updatedTransaction.currency, + '', + false, + ); + updatedMoneyRequestReport.lastMessageText = lastMessage[0].text; + updatedMoneyRequestReport.lastMessageHtml = lastMessage[0].html; + + // Update the last message of the chat report + const messageText = Localize.translateLocal('iou.payerOwesAmount', { + payer: updatedMoneyRequestReport.managerEmail, + amount: CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, updatedMoneyRequestReport.currency), + }); + updatedChatReport.lastMessageText = messageText; + updatedChatReport.lastMessageHtml = messageText; + } + // STEP 4: Compose the optimistic data const optimisticData = [ { @@ -1053,6 +1091,16 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: updatedTransaction, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: updatedMoneyRequestReport, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`, + value: updatedChatReport, + }, ]; const successData = [ @@ -1076,6 +1124,11 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC }, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: {pendingAction: null}, + }, ]; const failureData = [ @@ -1096,6 +1149,11 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: iouReport, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.chatReportID}`, + value: chatReport, + }, ]; // STEP 6: Call the API endpoint diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index 0d154809cd4d..0179c211ee55 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -182,7 +182,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { {content}