-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Allow split actions to be edited and implement IOU action completeSplitBill #29064
Changes from 67 commits
8c8d3ff
12b9043
7fba50a
bd7c4b2
4b95154
8f3f426
60bca0e
914985f
b3c4a3e
d62883c
08d7577
f516e3b
5812464
7950ce7
4096fe2
dd69105
46b6398
f134e3f
01d37b7
f495a29
5ad7a0a
322e20f
11883ed
20be465
9311976
1e71502
e3dc941
45d90fb
3231d67
cc9a41c
b87312d
6a8de3f
c25228c
ba0174c
4e54b1b
63733b2
a518d61
cdf376b
95dc96f
cc00910
5aad7a8
be6be0f
b274a70
56dc410
2f976ef
f80a658
6a28df3
1e21419
c7b5482
26a7311
48d684b
362a1ae
f8484ae
583a607
bf021a5
5867982
7a6f4b2
a740150
f73674d
0126d5f
9107a5a
272fb9b
5cd7d9b
95b3b9f
7945675
ddc2214
1a51105
69c0df9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,20 +84,25 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { | |
} | ||
|
||
function areRequiredFieldsEmpty(transaction: Transaction): boolean { | ||
return ( | ||
const isMerchantEmpty = | ||
transaction.merchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.merchant === ''; | ||
|
||
const isModifiedMerchantEmpty = | ||
!transaction.modifiedMerchant || | ||
transaction.modifiedMerchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || | ||
transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || | ||
(transaction.modifiedMerchant === '' && | ||
(transaction.merchant === CONST.TRANSACTION.UNKNOWN_MERCHANT || transaction.merchant === '' || transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT)) || | ||
(transaction.modifiedAmount === 0 && transaction.amount === 0) || | ||
(transaction.modifiedCreated === '' && transaction.created === '') | ||
); | ||
transaction.modifiedMerchant === ''; | ||
|
||
const isModifiedAmountEmpty = !transaction.modifiedAmount || transaction.modifiedAmount === 0; | ||
const isModifiedCreatedEmpty = !transaction.modifiedCreated || transaction.modifiedCreated === ''; | ||
|
||
return (isModifiedMerchantEmpty && isMerchantEmpty) || (isModifiedAmountEmpty && transaction.amount === 0) || (isModifiedCreatedEmpty && transaction.created === ''); | ||
} | ||
|
||
/** | ||
* Given the edit made to the money request, return an updated transaction object. | ||
*/ | ||
function getUpdatedTransaction(transaction: Transaction, transactionChanges: TransactionChanges, isFromExpenseReport: boolean): Transaction { | ||
function getUpdatedTransaction(transaction: Transaction, transactionChanges: TransactionChanges, isFromExpenseReport: boolean, shouldUpdateReceiptState = true): Transaction { | ||
// Only changing the first level fields so no need for deep clone now | ||
const updatedTransaction = {...transaction}; | ||
let shouldStopSmartscan = false; | ||
|
@@ -144,7 +149,13 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra | |
updatedTransaction.tag = transactionChanges.tag; | ||
} | ||
|
||
if (shouldStopSmartscan && transaction?.receipt && Object.keys(transaction.receipt).length > 0 && transaction?.receipt?.state !== CONST.IOU.RECEIPT_STATE.OPEN) { | ||
if ( | ||
shouldUpdateReceiptState && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why this function handles updating a receipt, the function name is |
||
shouldStopSmartscan && | ||
transaction?.receipt && | ||
Object.keys(transaction.receipt).length > 0 && | ||
transaction?.receipt?.state !== CONST.IOU.RECEIPT_STATE.OPEN | ||
) { | ||
updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; | ||
} | ||
|
||
|
@@ -438,6 +449,7 @@ export { | |
isPending, | ||
isPosted, | ||
getWaypoints, | ||
areRequiredFieldsEmpty, | ||
hasMissingSmartscanFields, | ||
getWaypointIndex, | ||
waypointHasValidAddress, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,15 @@ Onyx.connect({ | |
}, | ||
}); | ||
|
||
let allDraftSplitTransactions; | ||
Onyx.connect({ | ||
key: ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT, | ||
waitForCollectionCallback: true, | ||
callback: (val) => { | ||
allDraftSplitTransactions = val || {}; | ||
}, | ||
}); | ||
|
||
let allRecentlyUsedTags = {}; | ||
Onyx.connect({ | ||
key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, | ||
|
@@ -1489,6 +1498,229 @@ function startSplitBill(participants, currentUserLogin, currentUserAccountID, co | |
Report.notifyNewAction(splitChatReport.chatReportID, currentUserAccountID); | ||
} | ||
|
||
/** Used for editing a split bill while it's still scanning or when SmartScan fails, it completes a split bill started by startSplitBill above. | ||
* | ||
* @param {number} chatReportID - The group chat or workspace reportID | ||
* @param {Object} reportAction - The split action that lives in the chatReport above | ||
* @param {Object} updatedTransaction - The updated **draft** split transaction | ||
* @param {Number} sessionAccountID - accountID of the current user | ||
* @param {String} sessionEmail - email of the current user | ||
*/ | ||
function completeSplitBill(chatReportID, reportAction, updatedTransaction, sessionAccountID, sessionEmail) { | ||
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(sessionEmail); | ||
const {transactionID} = updatedTransaction; | ||
const unmodifiedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; | ||
|
||
// Save optimistic updated transaction and action | ||
const optimisticData = [ | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, | ||
value: { | ||
...updatedTransaction, | ||
receipt: { | ||
state: CONST.IOU.RECEIPT_STATE.OPEN, | ||
}, | ||
}, | ||
}, | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, | ||
value: { | ||
[reportAction.reportActionID]: { | ||
lastModified: DateUtils.getDBTime(), | ||
whisperedToAccountIDs: [], | ||
}, | ||
}, | ||
}, | ||
]; | ||
|
||
const successData = [ | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, | ||
value: {pendingAction: null}, | ||
}, | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, | ||
value: null, | ||
}, | ||
]; | ||
|
||
const failureData = [ | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, | ||
value: { | ||
...unmodifiedTransaction, | ||
errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), | ||
}, | ||
}, | ||
{ | ||
onyxMethod: Onyx.METHOD.MERGE, | ||
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, | ||
value: { | ||
[reportAction.reportActionID]: { | ||
...reportAction, | ||
errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericCreateFailureMessage'), | ||
}, | ||
}, | ||
}, | ||
]; | ||
|
||
const splitParticipants = updatedTransaction.comment.splits; | ||
const {modifiedAmount: amount, modifiedCurrency: currency} = updatedTransaction; | ||
|
||
// Exclude the current user when calculating the split amount, `calculateAmount` takes it into account | ||
const splitAmount = IOUUtils.calculateAmount(splitParticipants.length - 1, amount, currency, false); | ||
|
||
const splits = [{email: currentUserEmailForIOUSplit}]; | ||
_.each(splitParticipants, (participant) => { | ||
// Skip creating the transaction for the current user | ||
if (participant.email === currentUserEmailForIOUSplit) { | ||
return; | ||
} | ||
const isPolicyExpenseChat = !_.isEmpty(participant.policyID); | ||
|
||
if (!isPolicyExpenseChat) { | ||
// In case this is still the optimistic accountID saved in the splits array, return early as we cannot know | ||
// if there is an existing chat between the split creator and this participant | ||
// Instead, we will rely on Auth generating the report IDs and the user won't see any optimistic chats or reports created | ||
const participantPersonalDetails = allPersonalDetails[participant.accountID] || {}; | ||
if (!participantPersonalDetails || participantPersonalDetails.isOptimisticPersonalDetail) { | ||
splits.push({ | ||
email: participant.email, | ||
}); | ||
youssef-lr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return; | ||
} | ||
} | ||
|
||
let oneOnOneChatReport; | ||
let isNewOneOnOneChatReport = false; | ||
if (isPolicyExpenseChat) { | ||
// The workspace chat reportID is saved in the splits array when starting a split bill with a workspace | ||
oneOnOneChatReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`]; | ||
} else { | ||
const existingChatReport = ReportUtils.getChatByParticipants([participant.accountID]); | ||
isNewOneOnOneChatReport = !existingChatReport; | ||
oneOnOneChatReport = existingChatReport || ReportUtils.buildOptimisticChatReport([participant.accountID]); | ||
} | ||
|
||
let oneOnOneIOUReport = lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`, undefined); | ||
const shouldCreateNewOneOnOneIOUReport = | ||
_.isUndefined(oneOnOneIOUReport) || (isPolicyExpenseChat && ReportUtils.isControlPolicyExpenseReport(oneOnOneIOUReport) && ReportUtils.isReportApproved(oneOnOneIOUReport)); | ||
|
||
if (shouldCreateNewOneOnOneIOUReport) { | ||
oneOnOneIOUReport = isPolicyExpenseChat | ||
? ReportUtils.buildOptimisticExpenseReport(oneOnOneChatReport.reportID, participant.policyID, sessionAccountID, splitAmount, currency) | ||
: ReportUtils.buildOptimisticIOUReport(sessionAccountID, participant.accountID, splitAmount, oneOnOneChatReport.reportID, currency); | ||
} else if (isPolicyExpenseChat) { | ||
// Because of the Expense reports are stored as negative values, we subtract the total from the amount | ||
oneOnOneIOUReport.total -= splitAmount; | ||
} else { | ||
oneOnOneIOUReport = IOUUtils.updateIOUOwnerAndTotal(oneOnOneIOUReport, sessionAccountID, splitAmount, currency); | ||
} | ||
|
||
const oneOnOneTransaction = TransactionUtils.buildOptimisticTransaction( | ||
isPolicyExpenseChat ? -splitAmount : splitAmount, | ||
currency, | ||
oneOnOneIOUReport.reportID, | ||
updatedTransaction.comment.comment, | ||
updatedTransaction.modifiedCreated, | ||
CONST.IOU.MONEY_REQUEST_TYPE.SPLIT, | ||
transactionID, | ||
updatedTransaction.modifiedMerchant, | ||
{...updatedTransaction.receipt, state: CONST.IOU.RECEIPT_STATE.OPEN}, | ||
updatedTransaction.filename, | ||
); | ||
|
||
const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); | ||
const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); | ||
const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction( | ||
CONST.IOU.REPORT_ACTION_TYPE.CREATE, | ||
splitAmount, | ||
currency, | ||
updatedTransaction.comment.comment, | ||
[participant], | ||
oneOnOneTransaction.transactionID, | ||
'', | ||
oneOnOneIOUReport.reportID, | ||
); | ||
|
||
let oneOnOneReportPreviewAction = ReportActionsUtils.getReportPreviewAction(oneOnOneChatReport.reportID, oneOnOneIOUReport.reportID); | ||
if (oneOnOneReportPreviewAction) { | ||
oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction); | ||
} else { | ||
oneOnOneReportPreviewAction = ReportUtils.buildOptimisticReportPreview(oneOnOneChatReport, oneOnOneIOUReport, '', oneOnOneTransaction); | ||
} | ||
|
||
const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest( | ||
oneOnOneChatReport, | ||
oneOnOneIOUReport, | ||
oneOnOneTransaction, | ||
oneOnOneCreatedActionForChat, | ||
oneOnOneCreatedActionForIOU, | ||
oneOnOneIOUAction, | ||
{}, | ||
oneOnOneReportPreviewAction, | ||
{}, | ||
{}, | ||
isNewOneOnOneChatReport, | ||
shouldCreateNewOneOnOneIOUReport, | ||
); | ||
|
||
splits.push({ | ||
email: participant.email, | ||
accountID: participant.accountID, | ||
youssef-lr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
policyID: participant.policyID, | ||
iouReportID: oneOnOneIOUReport.reportID, | ||
chatReportID: oneOnOneChatReport.reportID, | ||
transactionID: oneOnOneTransaction.transactionID, | ||
reportActionID: oneOnOneIOUAction.reportActionID, | ||
createdChatReportActionID: oneOnOneCreatedActionForChat.reportActionID, | ||
createdIOUReportActionID: oneOnOneCreatedActionForIOU.reportActionID, | ||
reportPreviewReportActionID: oneOnOneReportPreviewAction.reportActionID, | ||
}); | ||
|
||
optimisticData.push(...oneOnOneOptimisticData); | ||
successData.push(...oneOnOneSuccessData); | ||
failureData.push(...oneOnOneFailureData); | ||
}); | ||
|
||
API.write( | ||
'CompleteSplitBill', | ||
{ | ||
transactionID, | ||
amount: updatedTransaction.modifiedAmount, | ||
currency: updatedTransaction.modifiedCurrency, | ||
created: updatedTransaction.modifiedCreated, | ||
merchant: updatedTransaction.modifiedMerchant, | ||
comment: updatedTransaction.comment.comment, | ||
splits: JSON.stringify(splits), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand this is backend related but why we are not sending the data as is? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't stringify the object the backend ends up receiving |
||
}, | ||
{optimisticData, successData, failureData}, | ||
); | ||
Navigation.dismissModal(chatReportID); | ||
Report.notifyNewAction(chatReportID, sessionAccountID); | ||
} | ||
|
||
/** | ||
* @param {String} transactionID | ||
* @param {Object} transactionChanges | ||
*/ | ||
function setDraftSplitTransaction(transactionID, transactionChanges = {}) { | ||
let draftSplitTransaction = allDraftSplitTransactions[`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`]; | ||
|
||
if (!draftSplitTransaction) { | ||
draftSplitTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; | ||
} | ||
|
||
const updatedTransaction = TransactionUtils.getUpdatedTransaction(draftSplitTransaction, transactionChanges, false, false); | ||
|
||
Onyx.merge(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, updatedTransaction); | ||
} | ||
|
||
/** | ||
* @param {String} transactionID | ||
* @param {Number} transactionThreadReportID | ||
|
@@ -2660,7 +2892,9 @@ export { | |
deleteMoneyRequest, | ||
splitBill, | ||
splitBillAndOpenReport, | ||
setDraftSplitTransaction, | ||
startSplitBill, | ||
completeSplitBill, | ||
requestMoney, | ||
sendMoneyElsewhere, | ||
approveMoneyRequest, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @luacmartins @mountiny can you guys check this change?
areRequiredFieldsEmpty
was returning false even tho we had a missing modifiedMerchant, because it doesn’t check for non existent modifiedMerchant in the object