Skip to content

Commit

Permalink
Merge pull request #32698 from rojiphil/26511-offline-money-request-i…
Browse files Browse the repository at this point in the history
…ssues

fix for offline money request issues
  • Loading branch information
blimpich authored Jan 9, 2024
2 parents 8457128 + 538b191 commit 35a6916
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 50 deletions.
25 changes: 13 additions & 12 deletions src/libs/IOUUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,20 @@ function updateIOUOwnerAndTotal(iouReport: OnyxEntry<Report>, actorAccountID: nu
// Make a copy so we don't mutate the original object
const iouReportUpdate: Report = {...iouReport};

if (iouReportUpdate.total) {
if (actorAccountID === iouReport.ownerAccountID) {
iouReportUpdate.total += isDeleting ? -amount : amount;
} else {
iouReportUpdate.total += isDeleting ? amount : -amount;
}
// Let us ensure a valid value before updating the total amount.
iouReportUpdate.total = iouReportUpdate.total ?? 0;

if (iouReportUpdate.total < 0) {
// The total sign has changed and hence we need to flip the manager and owner of the report.
iouReportUpdate.ownerAccountID = iouReport.managerID;
iouReportUpdate.managerID = iouReport.ownerAccountID;
iouReportUpdate.total = -iouReportUpdate.total;
}
if (actorAccountID === iouReport.ownerAccountID) {
iouReportUpdate.total += isDeleting ? -amount : amount;
} else {
iouReportUpdate.total += isDeleting ? amount : -amount;
}

if (iouReportUpdate.total < 0) {
// The total sign has changed and hence we need to flip the manager and owner of the report.
iouReportUpdate.ownerAccountID = iouReport.managerID;
iouReportUpdate.managerID = iouReport.ownerAccountID;
iouReportUpdate.total = -iouReportUpdate.total;
}

return iouReportUpdate;
Expand Down
16 changes: 15 additions & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,12 @@ function hasSingleParticipant(report: OnyxEntry<Report>): boolean {
*/
function hasOnlyDistanceRequestTransactions(iouReportID: string | undefined): boolean {
const transactions = TransactionUtils.getAllReportTransactions(iouReportID);

// Early return false in case not having any transaction
if (!transactions || transactions.length === 0) {
return false;
}

return transactions.every((transaction) => TransactionUtils.isDistanceRequest(transaction));
}

Expand Down Expand Up @@ -2910,10 +2916,10 @@ function buildOptimisticReportPreview(
accountID: iouReport?.managerID ?? 0,
// The preview is initially whispered if created with a receipt, so the actor is the current user as well
actorAccountID: hasReceipt ? currentUserAccountID : iouReport?.managerID ?? 0,
childReportID: childReportID ?? iouReport?.reportID,
childMoneyRequestCount: 1,
childLastMoneyRequestComment: comment,
childRecentReceiptTransactionIDs: hasReceipt && isNotEmptyObject(transaction) ? {[transaction?.transactionID ?? '']: created} : undefined,
childReportID,
whisperedToAccountIDs: isReceiptBeingScanned ? [currentUserAccountID ?? -1] : [],
};
}
Expand Down Expand Up @@ -3758,6 +3764,7 @@ function canRequestMoney(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>, o
if (isOwnExpenseReport && PolicyUtils.isPaidGroupPolicy(policy)) {
return isDraftExpenseReport(report);
}

return (isOwnExpenseReport || isIOUReport(report)) && !isReportApproved(report) && !isSettled(report?.reportID);
}

Expand Down Expand Up @@ -3926,6 +3933,13 @@ function getAddWorkspaceRoomOrChatReportErrors(report: OnyxEntry<Report>): Recor

function canUserPerformWriteAction(report: OnyxEntry<Report>) {
const reportErrors = getAddWorkspaceRoomOrChatReportErrors(report);
// If the Money Request report is marked for deletion, let us prevent any further write action.
if (isMoneyRequestReport(report)) {
const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? '');
if (parentReportAction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
return false;
}
}
return !isArchivedRoom(report) && isEmptyObject(reportErrors) && report && isAllowedToComment(report) && !isAnonymousUser;
}

Expand Down
90 changes: 57 additions & 33 deletions src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -2497,38 +2497,37 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
iouReportLastMessageText.length === 0 && !ReportActionsUtils.isDeletedParentAction(lastVisibleAction) && (!transactionThreadID || shouldDeleteTransactionThread);

// STEP 4: Update the iouReport and reportPreview with new totals and messages if it wasn't deleted
let updatedIOUReport = null;
let updatedReportPreviewAction = null;
if (!shouldDeleteIOUReport) {
if (ReportUtils.isExpenseReport(iouReport)) {
updatedIOUReport = {...iouReport};
let updatedIOUReport = {...iouReport};
const updatedReportPreviewAction = {...reportPreviewAction};
updatedReportPreviewAction.pendingAction = shouldDeleteIOUReport ? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE;
if (ReportUtils.isExpenseReport(iouReport)) {
updatedIOUReport = {...iouReport};

// Because of the Expense reports are stored as negative values, we add the total from the amount
updatedIOUReport.total += TransactionUtils.getAmount(transaction, true);
} else {
updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(
iouReport,
reportAction.actorAccountID,
TransactionUtils.getAmount(transaction, false),
TransactionUtils.getCurrency(transaction),
true,
);
}

// Because of the Expense reports are stored as negative values, we add the total from the amount
updatedIOUReport.total += TransactionUtils.getAmount(transaction, true);
} else {
updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(
iouReport,
reportAction.actorAccountID,
TransactionUtils.getAmount(transaction, false),
TransactionUtils.getCurrency(transaction),
true,
);
}
updatedIOUReport.lastMessageText = iouReportLastMessageText;
updatedIOUReport.lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created');

updatedIOUReport.lastMessageText = iouReportLastMessageText;
updatedIOUReport.lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created');
const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport);
const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', {
payer: ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || '',
amount: CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency),
});
updatedReportPreviewAction.message[0].text = messageText;
updatedReportPreviewAction.message[0].html = messageText;

updatedReportPreviewAction = {...reportPreviewAction};
const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(iouReport);
const messageText = Localize.translateLocal(hasNonReimbursableTransactions ? 'iou.payerSpentAmount' : 'iou.payerOwesAmount', {
payer: ReportUtils.getPersonalDetailsForAccountID(updatedIOUReport.managerID).login || '',
amount: CurrencyUtils.convertToDisplayString(updatedIOUReport.total, updatedIOUReport.currency),
});
updatedReportPreviewAction.message[0].text = messageText;
updatedReportPreviewAction.message[0].html = messageText;
if (reportPreviewAction.childMoneyRequestCount > 0) {
updatedReportPreviewAction.childMoneyRequestCount = reportPreviewAction.childMoneyRequestCount - 1;
}
if (reportPreviewAction.childMoneyRequestCount > 0) {
updatedReportPreviewAction.childMoneyRequestCount = reportPreviewAction.childMoneyRequestCount - 1;
}

// STEP 5: Build Onyx data
Expand Down Expand Up @@ -2562,12 +2561,12 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
]
: []),
{
onyxMethod: shouldDeleteIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: shouldDeleteIOUReport ? null : updatedReportAction,
value: updatedReportAction,
},
{
onyxMethod: shouldDeleteIOUReport ? Onyx.METHOD.SET : Onyx.METHOD.MERGE,
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: updatedIOUReport,
},
Expand Down Expand Up @@ -2610,9 +2609,34 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
value: {
[reportAction.reportActionID]: {pendingAction: null},
[reportAction.reportActionID]: shouldDeleteIOUReport
? null
: {
pendingAction: null,
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`,
value: {
[reportPreviewAction.reportActionID]: shouldDeleteIOUReport
? null
: {
pendingAction: null,
errors: null,
},
},
},
...(shouldDeleteIOUReport
? [
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
value: null,
},
]
: []),
];

const failureData = [
Expand Down
11 changes: 7 additions & 4 deletions tests/actions/IOUTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -1991,7 +1991,8 @@ describe('actions/IOU', () => {
});

createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
expect(createIOUAction).toBeFalsy();
// Then the IOU Action should be truthy for offline support.
expect(createIOUAction).toBeTruthy();

// Then we check if the transaction is removed from the transactions collection
const t = await new Promise((resolve) => {
Expand All @@ -2009,6 +2010,7 @@ describe('actions/IOU', () => {

// Given fetch operations are resumed
fetch.resume();
await waitForBatchedUpdates();

// Then we recheck the IOU report action from the report actions collection
reportActionsForReport = await new Promise((resolve) => {
Expand Down Expand Up @@ -2059,11 +2061,12 @@ describe('actions/IOU', () => {
});
});

// Then the report should be falsy (indicating deletion)
expect(report).toBeFalsy();
// Then the report should be truthy for offline support
expect(report).toBeTruthy();

// Given the resumed fetch state
fetch.resume();
await waitForBatchedUpdates();

report = await new Promise((resolve) => {
const connectionID = Onyx.connect({
Expand All @@ -2076,7 +2079,7 @@ describe('actions/IOU', () => {
});
});

// Then the report should still be falsy (confirming deletion persisted)
// Then the report should be falsy so that there is no trace of the money request.
expect(report).toBeFalsy();
});

Expand Down

0 comments on commit 35a6916

Please sign in to comment.