diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 8f544b5239fa..91f9e782930d 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -44,7 +44,7 @@ const defaultProps = { }; const MoneyRequestHeader = (props) => { - const formattedAmount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); + const formattedAmount = CurrencyUtils.convertToDisplayString(ReportUtils.getMoneyRequestTotal(props.report), props.report.currency); const isSettled = ReportUtils.isSettled(props.report.reportID); const isExpenseReport = ReportUtils.isExpenseReport(props.report); const payeeName = isExpenseReport ? ReportUtils.getPolicyName(props.report, props.policies) : ReportUtils.getDisplayNameForParticipant(props.report.managerEmail); diff --git a/src/components/ReportActionItem/IOUPreview.js b/src/components/ReportActionItem/IOUPreview.js index 775e121a776c..65cc6ed4929f 100644 --- a/src/components/ReportActionItem/IOUPreview.js +++ b/src/components/ReportActionItem/IOUPreview.js @@ -25,6 +25,7 @@ import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes import {showContextMenuForReport} from '../ShowContextMenuContext'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; +import * as ReportUtils from '../../libs/ReportUtils'; const propTypes = { /** The active IOUReport, used for Onyx subscription */ @@ -146,7 +147,7 @@ const IOUPreview = (props) => { const isCurrentUserManager = managerEmail === sessionEmail; // If props.action is undefined then we are displaying within IOUDetailsModal and should use the full report amount - const requestAmount = props.isIOUAction ? lodashGet(props.action, 'originalMessage.amount', 0) : props.iouReport.total; + const requestAmount = props.isIOUAction ? lodashGet(props.action, 'originalMessage.amount', 0) : ReportUtils.getMoneyRequestTotal(props.iouReport); const requestCurrency = props.isIOUAction ? lodashGet(props.action, 'originalMessage.currency', CONST.CURRENCY.USD) : props.iouReport.currency; const getSettledMessage = () => { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 043db320b3ac..24f209ff20c4 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -87,7 +87,7 @@ const defaultProps = { }; const ReportPreview = (props) => { - const reportAmount = CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency); + const reportAmount = CurrencyUtils.convertToDisplayString(ReportUtils.getMoneyRequestTotal(props.iouReport), props.iouReport.currency); const managerEmail = props.iouReport.managerEmail || ''; const managerName = ReportUtils.getDisplayNameForParticipant(managerEmail, true); const isCurrentUserManager = managerEmail === lodashGet(props.session, 'email', null); diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 66b38ab24e90..c8f83ed2f2a2 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -458,7 +458,7 @@ function createOption(logins, personalDetails, report, reportActions = {}, {show } result.isIOUReportOwner = ReportUtils.isIOUOwnedByCurrentUser(result, iouReports); - result.iouReportAmount = ReportUtils.getIOUTotal(result, iouReports); + result.iouReportAmount = ReportUtils.getMoneyRequestTotal(result, iouReports); if (!hasMultipleParticipants) { result.login = personalDetail.login; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index af8f07dc9f0a..15b17acf2924 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -863,6 +863,27 @@ function getDisplayNamesWithTooltips(participants, isMultipleParticipantReport) }); } +/** + * @param {Object} report + * @param {String} report.iouReportID + * @param {Object} moneyRequestReports + * @returns {Number} + */ +function getMoneyRequestTotal(report, moneyRequestReports = {}) { + if (report.hasOutstandingIOU || isMoneyRequestReport(report)) { + const moneyRequestReport = moneyRequestReports[`${ONYXKEYS.COLLECTION.REPORT}${report.iouReportID}`] || report; + const total = lodashGet(moneyRequestReport, 'total', 0); + + if (total !== 0) { + // There is a possibility that if the Expense report has a negative total. + // This is because there are instances where you can get a credit back on your card, + // or you enter a negative expense to “offset” future expenses + return isExpenseReport(moneyRequestReport) ? total * -1 : Math.abs(total); + } + } + return 0; +} + /** * Get the title for a policy expense chat which depends on the role of the policy member seeing this report * @@ -900,7 +921,7 @@ function getPolicyExpenseChatName(report) { * @returns {String} */ function getMoneyRequestReportName(report) { - const formattedAmount = CurrencyUtils.convertToDisplayString(report.total || 0, report.currency); + const formattedAmount = CurrencyUtils.convertToDisplayString(getMoneyRequestTotal(report), report.currency); const payerName = isExpenseReport(report) ? getPolicyName(report) : getDisplayNameForParticipant(report.managerEmail); return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName, amount: formattedAmount}); @@ -1116,8 +1137,10 @@ function buildOptimisticIOUReport(payeeEmail, payerEmail, total, chatReportID, c * @returns {Object} */ function buildOptimisticExpenseReport(chatReportID, policyID, payeeEmail, total, currency) { + // The amount for Expense reports are stored as negative value in the database + const storedTotal = total * -1; const policyName = getPolicyName(allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`]); - const formattedTotal = CurrencyUtils.convertToDisplayString(total, currency); + const formattedTotal = CurrencyUtils.convertToDisplayString(storedTotal, currency); // The expense report is always created with the policy's output currency const outputCurrency = lodashGet(allPolicies, [`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, 'outputCurrency'], CONST.CURRENCY.USD); @@ -1135,7 +1158,7 @@ function buildOptimisticExpenseReport(chatReportID, policyID, payeeEmail, total, reportName: `${policyName} owes ${formattedTotal}`, state: CONST.REPORT.STATE.SUBMITTED, stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - total, + total: storedTotal, }; } @@ -1528,22 +1551,6 @@ function hasOutstandingIOU(report, currentUserLogin, iouReports) { return report.hasOutstandingIOU; } -/** - * @param {Object} report - * @param {String} report.iouReportID - * @param {Object} iouReports - * @returns {Number} - */ -function getIOUTotal(report, iouReports = {}) { - if (report.hasOutstandingIOU) { - const iouReport = iouReports[`${ONYXKEYS.COLLECTION.REPORT}${report.iouReportID}`]; - if (iouReport) { - return iouReport.total; - } - } - return 0; -} - /** * @param {Object} report * @param {String} report.iouReportID @@ -1945,7 +1952,7 @@ export { hasExpensifyGuidesEmails, hasOutstandingIOU, isIOUOwnedByCurrentUser, - getIOUTotal, + getMoneyRequestTotal, canShowReportRecipientLocalTime, formatReportLastMessageText, chatIncludesConcierge, diff --git a/src/libs/SidebarUtils.js b/src/libs/SidebarUtils.js index ca208d67321b..621f55387e55 100644 --- a/src/libs/SidebarUtils.js +++ b/src/libs/SidebarUtils.js @@ -121,7 +121,7 @@ function getOrderedReportIDs(reportIDFromRoute) { report.displayName = ReportUtils.getReportName(report); // eslint-disable-next-line no-param-reassign - report.iouReportAmount = ReportUtils.getIOUTotal(report, moneyRequestReports); + report.iouReportAmount = ReportUtils.getMoneyRequestTotal(report, moneyRequestReports); }); // The LHN is split into five distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order: @@ -331,7 +331,7 @@ function getOptionData(reportID) { } result.isIOUReportOwner = ReportUtils.isIOUOwnedByCurrentUser(result, moneyRequestReports); - result.iouReportAmount = ReportUtils.getIOUTotal(result, moneyRequestReports); + result.iouReportAmount = ReportUtils.getMoneyRequestTotal(result, moneyRequestReports); if (!hasMultipleParticipants) { result.login = personalDetail.login; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index ea3d4bf1a9d3..b7f7dff45a6d 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -23,7 +23,7 @@ Onyx.connect({ if (!report) { delete iouReports[key]; delete chatReports[key]; - } else if (ReportUtils.isIOUReport(report)) { + } else if (ReportUtils.isMoneyRequestReport(report)) { iouReports[key] = report; } else { chatReports[key] = report; @@ -76,7 +76,9 @@ function requestMoney(report, amount, currency, payeeEmail, participant, comment if (chatReport.iouReportID) { if (isPolicyExpenseChat) { moneyRequestReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]}; - moneyRequestReport.total += amount; + + // Because of the Expense reports are stored as negative values, we substract the total from the amount + moneyRequestReport.total = ReportUtils.isExpenseReport(moneyRequestReport) ? moneyRequestReport.total - amount : moneyRequestReport.total + amount; } else { moneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency); } @@ -776,7 +778,7 @@ function setMoneyRequestDescription(comment) { * @returns {String} */ function buildPayPalPaymentUrl(amount, submitterPayPalMeAddress, currency) { - return `https://paypal.me/${submitterPayPalMeAddress}/${amount / 100}${currency}`; + return `https://paypal.me/${submitterPayPalMeAddress}/${Math.abs(amount) / 100}${currency}`; } /** diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 10658ec79137..2f929cb4e1a6 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -396,8 +396,8 @@ describe('Sidebar', () => { expect(screen.queryAllByTestId('Pin Icon')).toHaveLength(1); expect(screen.queryAllByTestId('Pencil Icon')).toHaveLength(1); expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('One, Two'); - expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Five, Six'); - expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Five, Six'); expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Three, Four'); }) ); @@ -733,11 +733,11 @@ describe('Sidebar', () => { const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNames = screen.queryAllByLabelText(hintText); expect(displayNames).toHaveLength(5); - expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Five, Six'); - expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('One, Two'); - expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Three, Four'); - expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Email Two owes $100.00'); - expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [0, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [1, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [2, 'props', 'children'])).toBe('Email Two owes $100.00'); + expect(lodashGet(displayNames, [3, 'props', 'children'])).toBe('Five, Six'); + expect(lodashGet(displayNames, [4, 'props', 'children'])).toBe('One, Two'); }) ); });