diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index 7efc36baf8ae..443944264858 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -16,6 +16,11 @@ import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; import * as IOUUtils from '../../libs/IOUUtils'; +import * as OptionsListUtils from '../../libs/OptionsListUtils'; +import * as ReportUtils from '../../libs/ReportUtils'; +import * as Report from '../../libs/actions/Report'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import * as CurrencyUtils from '../../libs/CurrencyUtils'; const propTypes = { /** All the data of the action */ @@ -56,6 +61,14 @@ const propTypes = { isHovered: PropTypes.bool, network: networkPropTypes.isRequired, + + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user email */ + email: PropTypes.string, + }), + + ...withLocalizePropTypes, }; const defaultProps = { @@ -67,15 +80,46 @@ const defaultProps = { iouReport: {}, reportActions: {}, isHovered: false, + session: { + email: null, + }, }; const MoneyRequestAction = (props) => { const hasMultipleParticipants = lodashGet(props.chatReport, 'participants', []).length > 1; const onIOUPreviewPressed = () => { - if (hasMultipleParticipants) { + if (lodashGet(props.action, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT && hasMultipleParticipants) { Navigation.navigate(ROUTES.getReportParticipantsRoute(props.chatReportID)); + return; + } + + // If the childReportID is not present, we need to create a new thread + const childReportID = lodashGet(props.action, 'childReportID', '0'); + if (childReportID === '0') { + const participants = _.uniq([props.session.email, props.action.actorEmail]); + const formattedUserLogins = _.map(participants, (login) => OptionsListUtils.addSMSDomainIfPhoneNumber(login).toLowerCase()); + const thread = ReportUtils.buildOptimisticChatReport( + formattedUserLogins, + props.translate('iou.threadReportName', { + formattedAmount: CurrencyUtils.convertToDisplayString(lodashGet(props.action, 'originalMessage.amount', 0), lodashGet(props.action, 'originalMessage.currency', '')), + comment: props.action.originalMessage.comment, + }), + '', + CONST.POLICY.OWNER_EMAIL_FAKE, + CONST.POLICY.OWNER_EMAIL_FAKE, + false, + '', + undefined, + CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + props.action.reportActionID, + props.chatReportID, // Needs to be changed to iouReportID + ); + + Report.openReport(thread.reportID, thread.participants, thread, props.action.reportActionID); + Navigation.navigate(ROUTES.getReportRoute(thread.reportID)); } else { - Navigation.navigate(ROUTES.getIouDetailsRoute(props.chatReportID, props.action.originalMessage.IOUReportID)); + Report.openReport(childReportID); + Navigation.navigate(ROUTES.getReportRoute(childReportID)); } }; @@ -125,6 +169,7 @@ MoneyRequestAction.defaultProps = defaultProps; MoneyRequestAction.displayName = 'MoneyRequestAction'; export default compose( + withLocalize, withOnyx({ chatReport: { key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, @@ -136,6 +181,9 @@ export default compose( key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, canEvict: false, }, + session: { + key: ONYXKEYS.SESSION, + }, }), withNetwork(), )(MoneyRequestAction); diff --git a/src/languages/en.js b/src/languages/en.js index c3543cd926e1..f9d9eaf23826 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -333,6 +333,7 @@ export default { payerSettled: ({amount}) => `settled up ${amount}`, noReimbursableExpenses: 'This report has an invalid amount', pendingConversionMessage: "Total will update when you're back online", + threadReportName: ({formattedAmount, comment}) => `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', diff --git a/src/languages/es.js b/src/languages/es.js index b936272d4622..23114b5bc428 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -332,6 +332,7 @@ export default { payerSettled: ({amount}) => `pagado ${amount}`, noReimbursableExpenses: 'El monto de este informe es inválido', pendingConversionMessage: 'El total se actualizará cuando estés online', + threadReportName: ({formattedAmount, comment}) => `Solicitud de ${formattedAmount}${comment ? ` para ${comment}` : ''}`, error: { invalidSplit: 'La suma de las partes no equivale al monto total', other: 'Error inesperado, por favor inténtalo más tarde', diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index e4ee0c1a899f..558afca9904e 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -53,6 +53,19 @@ function getParentReportAction(report) { return lodashGet(allReportActions, [report.parentReportID, report.parentReportActionID], {}); } +/** + * Returns whether the thread is a transaction thread, which is any thread with IOU parent + * report action of type create. + * + * @param {Object} parentReportAction + * @returns {Boolean} + */ +function isTransactionThread(parentReportAction) { + return ( + parentReportAction && parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && lodashGet(parentReportAction, 'originalMessage.type') === CONST.IOU.REPORT_ACTION_TYPE.CREATE + ); +} + /** * Sort an array of reportActions by their created timestamp first, and reportActionID second * This gives us a stable order even in the case of multiple reportActions created on the same millisecond @@ -322,4 +335,5 @@ export { getLinkedTransactionID, isCreatedTaskReportAction, getParentReportAction, + isTransactionThread, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 08ea0356eac6..8fa24c8fb75c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1001,6 +1001,19 @@ function getMoneyRequestReportName(report) { return Localize.translateLocal('iou.payerOwesAmount', {payer: payerName, amount: formattedAmount}); } +/** + * Given a parent IOU report action get report name for the LHN. + * + * @param {Object} reportAction + * @returns {String} + */ +function getTransactionReportName(reportAction) { + return Localize.translateLocal('iou.threadReportName', { + formattedAmount: CurrencyUtils.convertToDisplayString(lodashGet(reportAction, 'originalMessage.amount', 0), lodashGet(reportAction, 'originalMessage.currency', '')), + comment: lodashGet(reportAction, 'originalMessage.comment'), + }); +} + /** * Get the title for a report. * @@ -1011,6 +1024,9 @@ function getReportName(report) { let formattedName; if (isThread(report)) { const parentReportAction = ReportActionsUtils.getParentReportAction(report); + if (ReportActionsUtils.isTransactionThread(parentReportAction)) { + return getTransactionReportName(parentReportAction); + } const parentReportActionMessage = lodashGet(parentReportAction, ['message', 0, 'text'], '').replace(/(\r\n|\n|\r)/gm, ' '); return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); } @@ -1388,8 +1404,8 @@ function buildOptimisticChatReport( oldPolicyName = '', visibility = undefined, notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, - parentReportActionID, - parentReportID, + parentReportActionID = '', + parentReportID = '', ) { const currentTime = DateUtils.getDBTime(); return { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 6e8e5e0f962b..cc965e19871c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -404,6 +404,20 @@ function openReport(reportID, participantList = [], newReportObject = {}, parent // Add the createdReportActionID parameter to the API call params.createdReportActionID = optimisticCreatedAction.reportActionID; } + + // If we are creating a thread, ensure the report action has childReportID property added + if (newReportObject.parentReportID && parentReportActionID) { + onyxData.optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportObject.parentReportID}`, + value: {[parentReportActionID]: {childReportID: reportID}}, + }); + onyxData.failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newReportObject.parentReportID}`, + value: {[parentReportActionID]: {childReportID: '0'}}, + }); + } } API.write('OpenReport', params, onyxData); diff --git a/src/pages/home/report/ReportActionItemParentAction.js b/src/pages/home/report/ReportActionItemParentAction.js index 0714265a7899..cc50b62a1774 100644 --- a/src/pages/home/report/ReportActionItemParentAction.js +++ b/src/pages/home/report/ReportActionItemParentAction.js @@ -14,6 +14,7 @@ import compose from '../../../libs/compose'; import withLocalize from '../../../components/withLocalize'; import ReportActionItem from './ReportActionItem'; import reportActionPropTypes from './reportActionPropTypes'; +import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; const propTypes = { /** The id of the report */ @@ -41,6 +42,11 @@ const defaultProps = { const ReportActionItemParentAction = (props) => { const parentReportAction = props.parentReportActions[`${props.report.parentReportActionID}`]; + + // In case of transaction threads, we do not want to render the parent report action. + if (ReportActionsUtils.isTransactionThread(parentReportAction)) { + return null; + } return (