Skip to content

Commit

Permalink
Merge pull request #25498 from BeeMargarida/ms/23961-optimistic_data_…
Browse files Browse the repository at this point in the history
…iou_total_update_after_edit

Followup #23961: update IOU report total in optimistic data on edit money request
  • Loading branch information
mountiny authored Sep 14, 2023
2 parents 207945c + 58346d1 commit 1a49026
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 32 deletions.
49 changes: 30 additions & 19 deletions src/components/LHNOptionsList/OptionRowLHNData.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -56,8 +60,9 @@ const defaultProps = {
shouldDisableFocusOptions: false,
personalDetails: {},
fullReport: {},
policies: {},
policy: {},
parentReportActions: {},
transaction: {},
preferredLocale: CONST.LOCALES.DEFAULT,
...withCurrentReportIDDefaultProps,
...baseDefaultProps,
Expand All @@ -77,18 +82,17 @@ function OptionRowLHNData({
personalDetails,
preferredLocale,
comment,
policies,
policy,
receiptTransactions,
parentReportActions,
transaction,
...propsToForward
}) {
const reportID = propsToForward.reportID;
// We only want to pass a boolean to the memoized component,
// 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();
Expand All @@ -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) {
Expand Down Expand Up @@ -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),
);
42 changes: 30 additions & 12 deletions src/components/MoneyRequestHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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,
};

Expand All @@ -51,6 +61,8 @@ const defaultProps = {
email: null,
},
parentReport: {},
parentReportAction: {},
transaction: {},
};

function MoneyRequestHeader(props) {
Expand All @@ -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 (
<>
Expand All @@ -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),
},
]}
Expand Down Expand Up @@ -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);
58 changes: 58 additions & 0 deletions src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand All @@ -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 = [
Expand All @@ -1076,6 +1124,11 @@ function editMoneyRequest(transactionID, transactionThreadReportID, transactionC
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: {pendingAction: null},
},
];

const failureData = [
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/pages/iou/steps/NewRequestAmountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) {
<View style={[styles.flex1, safeAreaPaddingBottomStyle]}>
<HeaderWithBackButton
title={translate('iou.amount')}
onBackButonBackButtonPress={navigateBack}
onBackButtonPress={navigateBack}
/>
{content}
</View>
Expand Down

0 comments on commit 1a49026

Please sign in to comment.