Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable RequestMoney in Workspace chats #18215

Merged
merged 22 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 48 additions & 11 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1019,16 +1019,16 @@ function buildOptimisticAddCommentReportAction(text, file) {
/**
* Builds an optimistic IOU report with a randomly generated reportID
*
* @param {String} ownerEmail - Email of the person generating the IOU.
* @param {String} userEmail - Email of the other person participating in the IOU.
* @param {String} payeeEmail - Email of the person generating the IOU.
* @param {String} payerEmail - Email of the other person participating in the IOU.
* @param {Number} total - IOU amount in the smallest unit of the currency.
* @param {String} chatReportID - Report ID of the chat where the IOU is.
* @param {String} currency - IOU currency.
* @param {Boolean} isSendingMoney - If we send money the IOU should be created as settled
*
* @returns {Object}
*/
function buildOptimisticIOUReport(ownerEmail, userEmail, total, chatReportID, currency, isSendingMoney = false) {
function buildOptimisticIOUReport(payeeEmail, payerEmail, total, chatReportID, currency, isSendingMoney = false) {
const formattedTotal = CurrencyUtils.convertToDisplayString(total, currency);
return {
// If we're sending money, hasOutstandingIOU should be false
Expand All @@ -1037,26 +1037,63 @@ function buildOptimisticIOUReport(ownerEmail, userEmail, total, chatReportID, cu
cachedTotal: formattedTotal,
chatReportID,
currency,
managerEmail: userEmail,
ownerEmail,
managerEmail: payerEmail,
ownerEmail: payeeEmail,
reportID: generateReportID(),
state: CONST.REPORT.STATE.SUBMITTED,
stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.PROCESSING,
total,

// We don't translate reportName because the server response is always in English
reportName: `${payerEmail} owes ${formattedTotal}`,
};
}

/**
* Builds an optimistic Expense report with a randomly generated reportID
*
* @param {String} chatReportID - Report ID of the PolicyExpenseChat where the Expense Report is
* @param {String} policyID - The policy ID of the PolicyExpenseChat
* @param {String} payeeEmail - Email of the employee (payee)
* @param {Number} total - Amount in cents
* @param {String} currency
*
* @returns {Object}
*/
function buildOptimisticExpenseReport(chatReportID, policyID, payeeEmail, total, currency) {
const policyName = getPolicyName(allReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`]);
const formattedTotal = CurrencyUtils.convertToDisplayString(total, 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);

return {
reportID: generateReportID(),
chatReportID,
policyID,
type: CONST.REPORT.TYPE.EXPENSE,
ownerEmail: payeeEmail,
hasOutstandingIOU: true,
currency: outputCurrency,

// We don't translate reportName because the server response is always in English
reportName: `${policyName} owes ${formattedTotal}`,
state: CONST.REPORT.STATE.SUBMITTED,
stateNum: CONST.REPORT.STATE_NUM.PROCESSING,
Comment on lines +1081 to +1082
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state and the stateNum are not the same, I think they should be the same right?
I also think that state is not really used, maybe we should remove it.

This is also inconsistent here:

image

copy/paste?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm I dont think they should be same/identical, right? @luacmartins

Copy link
Contributor Author

@luacmartins luacmartins Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I don't know why we have state and we don't seem to use state for anything in App, so I agree that we can remove it to make it less confusing.

total,
};
}

/**
* @param {String} type - IOUReportAction type. Can be oneOf(create, delete, pay, split)
* @param {String} type - IOUReportAction type. Can be oneOf(create, decline, cancel, pay, split)
* @param {Number} total - IOU total in cents
* @param {Array} participants - List of logins for the IOU participants, excluding the current user login
* @param {String} comment - IOU comment
* @param {String} currency - IOU currency
* @param {String} paymentType - IOU paymentMethodType. Can be oneOf(Elsewhere, Expensify, PayPal.me)
* @param {Boolean} isSettlingUp - Whether we are settling up an IOU
* @returns {Array}
*/
function getIOUReportActionMessage(type, total, participants, comment, currency, paymentType = '', isSettlingUp = false) {
function getIOUReportActionMessage(type, total, comment, currency, paymentType = '', isSettlingUp = false) {
const amount = NumberFormatUtils.format(preferredLocale, total / 100, {style: 'currency', currency});
let paymentMethodMessage;
switch (paymentType) {
Expand Down Expand Up @@ -1108,14 +1145,13 @@ function getIOUReportActionMessage(type, total, participants, comment, currency,
* @param {Number} amount - IOU amount in cents.
* @param {String} currency
* @param {String} comment - User comment for the IOU.
* @param {Array} participants - An array with participants details.
* @param {String} transactionID
* @param {String} [paymentType] - Only required if the IOUReportAction type is 'pay'. Can be oneOf(elsewhere, payPal, Expensify).
* @param {String} [iouReportID] - Only required if the IOUReportActions type is oneOf(decline, cancel, pay). Generates a randomID as default.
* @param {Boolean} [isSettlingUp] - Whether we are settling up an IOU.
* @returns {Object}
*/
function buildOptimisticIOUReportAction(type, amount, currency, comment, participants, transactionID, paymentType = '', iouReportID = '', isSettlingUp = false) {
function buildOptimisticIOUReportAction(type, amount, currency, comment, transactionID, paymentType = '', iouReportID = '', isSettlingUp = false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change needs reverted.
Right now app crashes on main because of this line which was added in #18656

originalMessage.participants = [currentUserEmail, ..._.pluck(participants, 'login')];

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const IOUReportID = iouReportID || generateReportID();
const parser = new ExpensiMark();
const commentText = getParsedComment(comment);
Expand Down Expand Up @@ -1152,7 +1188,7 @@ function buildOptimisticIOUReportAction(type, amount, currency, comment, partici
avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)),
isAttachment: false,
originalMessage,
message: getIOUReportActionMessage(type, amount, participants, textForNewCommentDecoded, currency, paymentType, isSettlingUp),
message: getIOUReportActionMessage(type, amount, textForNewCommentDecoded, currency, paymentType, isSettlingUp),
person: [
{
style: 'strong',
Expand Down Expand Up @@ -1858,6 +1894,7 @@ export {
buildOptimisticClosedReportAction,
buildOptimisticCreatedReportAction,
buildOptimisticIOUReport,
buildOptimisticExpenseReport,
buildOptimisticIOUReportAction,
buildOptimisticAddCommentReportAction,
shouldReportBeInOptionList,
Expand Down
56 changes: 33 additions & 23 deletions src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,44 @@ Onyx.connect({
* @param {Object} report
* @param {Number} amount - always in the smallest unit of the currency
* @param {String} currency
* @param {String} recipientEmail
* @param {String} payeeEmail
* @param {Object} participant
* @param {String} comment
*/
function requestMoney(report, amount, currency, recipientEmail, participant, comment) {
const debtorEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login);
function requestMoney(report, amount, currency, payeeEmail, participant, comment) {
const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login);
let chatReport = lodashGet(report, 'reportID', null) ? report : null;
const isPolicyExpenseChat = participant.isPolicyExpenseChat || participant.isOwnPolicyExpenseChat;
let isNewChat = false;

// If this is a policyExpenseChat, the chatReport must exist and we can get it from Onyx.
// report is null if the flow is initiated from the global create menu. However, participant always stores the reportID if it exists, which is the case for policyExpenseChats
if (!chatReport && isPolicyExpenseChat) {
chatReport = chatReports[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`];
}

if (!chatReport) {
chatReport = ReportUtils.getChatByParticipants([debtorEmail]);
chatReport = ReportUtils.getChatByParticipants([payerEmail]);
}
if (!chatReport) {
chatReport = ReportUtils.buildOptimisticChatReport([debtorEmail]);
chatReport = ReportUtils.buildOptimisticChatReport([payerEmail]);
isNewChat = true;
}
let iouReport;
let moneyRequestReport;
if (chatReport.iouReportID) {
iouReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], recipientEmail, amount, currency);
if (isPolicyExpenseChat) {
moneyRequestReport = {...iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`]};
moneyRequestReport.total += amount;
} else {
moneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReports[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.iouReportID}`], payeeEmail, amount, currency);
}
} else {
iouReport = ReportUtils.buildOptimisticIOUReport(recipientEmail, debtorEmail, amount, chatReport.reportID, currency);
moneyRequestReport = isPolicyExpenseChat
? ReportUtils.buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID, payeeEmail, amount, currency)
: ReportUtils.buildOptimisticIOUReport(payeeEmail, payerEmail, amount, chatReport.reportID, currency);
}

const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment);
const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, moneyRequestReport.reportID, comment);
const optimisticTransactionData = {
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`,
Expand All @@ -95,16 +110,15 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com
};

// Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat
const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail);
const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail);
const optimisticReportAction = ReportUtils.buildOptimisticIOUReportAction(
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
amount,
currency,
comment,
[participant],
optimisticTransaction.transactionID,
'',
iouReport.reportID,
moneyRequestReport.reportID,
);

// First, add data that will be used in all cases
Expand All @@ -116,15 +130,15 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com
lastReadTime: DateUtils.getDBTime(),
lastMessageText: optimisticReportAction.message[0].text,
lastMessageHtml: optimisticReportAction.message[0].html,
hasOutstandingIOU: iouReport.total !== 0,
iouReportID: iouReport.reportID,
hasOutstandingIOU: moneyRequestReport.total !== 0,
iouReportID: moneyRequestReport.reportID,
},
};

const optimisticIOUReportData = {
onyxMethod: chatReport.hasOutstandingIOU ? Onyx.METHOD.MERGE : Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: iouReport,
key: `${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport.reportID}`,
value: moneyRequestReport,
};

const optimisticReportActionsData = {
Expand Down Expand Up @@ -214,11 +228,11 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com
API.write(
'RequestMoney',
{
debtorEmail,
debtorEmail: payerEmail,
amount,
currency,
comment: parsedComment,
iouReportID: iouReport.reportID,
iouReportID: moneyRequestReport.reportID,
chatReportID: chatReport.reportID,
transactionID: optimisticTransaction.transactionID,
reportActionID: optimisticReportAction.reportActionID,
Expand Down Expand Up @@ -271,7 +285,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment

// Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat
const groupCreatedReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail);
const groupIOUReportAction = ReportUtils.buildOptimisticIOUReportAction(CONST.IOU.REPORT_ACTION_TYPE.SPLIT, amount, currency, comment, participants, groupTransaction.transactionID);
const groupIOUReportAction = ReportUtils.buildOptimisticIOUReportAction(CONST.IOU.REPORT_ACTION_TYPE.SPLIT, amount, currency, comment, groupTransaction.transactionID);

groupChatReport.lastReadTime = DateUtils.getDBTime();
groupChatReport.lastMessageText = groupIOUReportAction.message[0].text;
Expand Down Expand Up @@ -410,7 +424,6 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment
splitAmount,
currency,
comment,
[participant],
oneOnOneTransaction.transactionID,
'',
oneOnOneIOUReport.reportID,
Expand Down Expand Up @@ -637,7 +650,6 @@ function deleteMoneyRequest(chatReportID, iouReportID, moneyRequestAction, shoul
amount,
moneyRequestAction.originalMessage.currency,
Str.htmlDecode(moneyRequestAction.originalMessage.comment),
[],
transactionID,
'',
iouReportID,
Expand Down Expand Up @@ -810,7 +822,6 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType
amount,
currency,
comment,
[recipient],
optimisticTransaction.transactionID,
paymentMethodType,
optimisticIOUReport.reportID,
Expand Down Expand Up @@ -951,7 +962,6 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho
iouReport.total,
iouReport.currency,
'',
[recipient],
optimisticTransaction.transactionID,
paymentMethodType,
iouReport.reportID,
Expand Down
6 changes: 1 addition & 5 deletions src/pages/iou/MoneyRequestModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,7 @@ const MoneyRequestModal = (props) => {
IOU.splitBillAndOpenReport(selectedParticipants, props.currentUserPersonalDetails.login, amount, trimmedComment, props.iou.selectedCurrencyCode);
return;
}
if (!selectedParticipants[0].login) {
// TODO - request to the policy expense chat. Not implemented yet!
// Will be implemented here: https://github.com/Expensify/Expensify/issues/270581
return;
}

IOU.requestMoney(props.report, amount, props.iou.selectedCurrencyCode, props.currentUserPersonalDetails.login, selectedParticipants[0], trimmedComment);
},
[amount, props.iou.comment, props.currentUserPersonalDetails.login, props.hasMultipleParticipants, props.iou.selectedCurrencyCode, props.report, props.route],
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/IOUUtilsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const ownerEmail = 'owner@iou.com';
const managerEmail = 'manager@iou.com';

function createIOUReportAction(type, amount, currency, isOffline = false, IOUTransactionID = NumberUtils.rand64()) {
const moneyRequestAction = ReportUtils.buildOptimisticIOUReportAction(type, amount, currency, 'Test comment', [managerEmail], IOUTransactionID, '', iouReport.reportID);
const moneyRequestAction = ReportUtils.buildOptimisticIOUReportAction(type, amount, currency, 'Test comment', IOUTransactionID, '', iouReport.reportID);

// Default is to create requests online, if `isOffline` is not specified then we need to remove the pendingAction
if (!isOffline) {
Expand Down