Skip to content

Commit fe2137f

Browse files
Merge pull request Expensify#64023 from Expensify/cmartins-fixOfflineBehavior
Fix offline behavior when reporting transaction
2 parents 7d60b11 + c969a6f commit fe2137f

2 files changed

Lines changed: 134 additions & 10 deletions

File tree

src/libs/actions/Transaction.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import * as CollectionUtils from '@libs/CollectionUtils';
1111
import DateUtils from '@libs/DateUtils';
1212
import * as NumberUtils from '@libs/NumberUtils';
1313
import {rand64} from '@libs/NumberUtils';
14-
import {getAllReportActions, getIOUActionForReportID, getOriginalMessage, isModifiedExpenseAction} from '@libs/ReportActionsUtils';
14+
import {getAllReportActions, getIOUActionForReportID, getOriginalMessage, getTrackExpenseActionableWhisper, isModifiedExpenseAction} from '@libs/ReportActionsUtils';
1515
import {
1616
buildOptimisticCreatedReportAction,
1717
buildOptimisticDismissedViolationReportAction,
1818
buildOptimisticMovedTransactionAction,
1919
buildOptimisticUnreportedTransactionAction,
2020
buildTransactionThread,
21+
findSelfDMReportID,
2122
} from '@libs/ReportUtils';
2223
import {getAmount, waypointHasValidAddress} from '@libs/TransactionUtils';
2324
import CONST from '@src/CONST';
@@ -614,7 +615,12 @@ function changeTransactionsReport(transactionIDs: string[], reportID: string) {
614615
let transactionsMoved = false;
615616

616617
transactions.forEach((transaction) => {
617-
const oldIOUAction = getIOUActionForReportID(transaction.reportID, transaction.transactionID);
618+
const isUnreported = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
619+
620+
// We'll handle optimistically creating the selfDM as part of https://github.com/Expensify/App/issues/60288
621+
const selfDMReportID = findSelfDMReportID() ?? CONST.REPORT.UNREPORTED_REPORT_ID;
622+
623+
const oldIOUAction = getIOUActionForReportID(isUnreported ? selfDMReportID : transaction.reportID, transaction.transactionID);
618624
if (!transaction.reportID || transaction.reportID === reportID) {
619625
return;
620626
}
@@ -651,7 +657,7 @@ function changeTransactionsReport(transactionIDs: string[], reportID: string) {
651657

652658
// 2. Keep track of the new report totals
653659
const transactionAmount = getAmount(transaction);
654-
if (oldReportID) {
660+
if (oldReport) {
655661
updatedReportTotals[oldReportID] = (updatedReportTotals[oldReportID] ? updatedReportTotals[oldReportID] : (oldReport?.total ?? 0)) + transactionAmount;
656662
}
657663
if (reportID) {
@@ -669,6 +675,8 @@ function changeTransactionsReport(transactionIDs: string[], reportID: string) {
669675
created: oldIOUAction?.created ?? DateUtils.getDBTime(),
670676
};
671677

678+
const trackExpenseActionableWhisper = isUnreported ? getTrackExpenseActionableWhisper(transaction.transactionID, selfDMReportID) : undefined;
679+
672680
if (oldIOUAction) {
673681
optimisticData.push({
674682
onyxMethod: Onyx.METHOD.MERGE,
@@ -680,7 +688,7 @@ function changeTransactionsReport(transactionIDs: string[], reportID: string) {
680688

681689
optimisticData.push({
682690
onyxMethod: Onyx.METHOD.MERGE,
683-
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldReportID}`,
691+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${isUnreported ? selfDMReportID : oldReportID}`,
684692
value: {
685693
[oldIOUAction.reportActionID]: {
686694
previousMessage: oldIOUAction.message,
@@ -698,6 +706,7 @@ function changeTransactionsReport(transactionIDs: string[], reportID: string) {
698706
},
699707
errors: undefined,
700708
},
709+
...(trackExpenseActionableWhisper ? {[trackExpenseActionableWhisper.reportActionID]: null} : {}),
701710
},
702711
});
703712
}
@@ -720,8 +729,11 @@ function changeTransactionsReport(transactionIDs: string[], reportID: string) {
720729
},
721730
{
722731
onyxMethod: Onyx.METHOD.MERGE,
723-
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oldReportID}`,
724-
value: {[oldIOUAction.reportActionID]: oldIOUAction},
732+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${isUnreported ? selfDMReportID : oldReportID}`,
733+
value: {
734+
[oldIOUAction.reportActionID]: oldIOUAction,
735+
...(trackExpenseActionableWhisper ? {[trackExpenseActionableWhisper.reportActionID]: trackExpenseActionableWhisper} : {}),
736+
},
725737
},
726738
);
727739
}
@@ -742,9 +754,9 @@ function changeTransactionsReport(transactionIDs: string[], reportID: string) {
742754
onyxMethod: Onyx.METHOD.MERGE,
743755
key: `${ONYXKEYS.COLLECTION.REPORT}${oldIOUAction.childReportID}`,
744756
value: {
745-
parentReportID: oldReportID,
757+
parentReportID: isUnreported ? selfDMReportID : oldReportID,
746758
optimisticMoneyRequestReportActionID: oldIOUAction.reportActionID,
747-
policyID: allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oldReportID}`]?.policyID,
759+
policyID: allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oldIOUAction.reportActionID}`]?.policyID,
748760
},
749761
});
750762
}

tests/actions/IOUTest.ts

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
updateSplitExpenseAmountField,
3434
} from '@libs/actions/IOU';
3535
import {createWorkspace, deleteWorkspace, generatePolicyID, setWorkspaceApprovalMode} from '@libs/actions/Policy/Policy';
36-
import {addComment, deleteReport, notifyNewAction, openReport} from '@libs/actions/Report';
36+
import {addComment, createNewReport, deleteReport, notifyNewAction, openReport} from '@libs/actions/Report';
3737
import {clearAllRelatedReportActionErrors} from '@libs/actions/ReportActions';
3838
import {subscribeToUserEvents} from '@libs/actions/User';
3939
import {WRITE_COMMANDS} from '@libs/API/types';
@@ -63,7 +63,7 @@ import * as API from '@src/libs/API';
6363
import DateUtils from '@src/libs/DateUtils';
6464
import ONYXKEYS from '@src/ONYXKEYS';
6565
import ROUTES from '@src/ROUTES';
66-
import type {Policy, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx';
66+
import type {PersonalDetailsList, Policy, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx';
6767
import type {Accountant} from '@src/types/onyx/IOU';
6868
import type {Participant, ReportCollectionDataSet} from '@src/types/onyx/Report';
6969
import type {ReportActions, ReportActionsCollectionDataSet} from '@src/types/onyx/ReportAction';
@@ -72,6 +72,7 @@ import type {TransactionCollectionDataSet} from '@src/types/onyx/Transaction';
7272
import type Transaction from '@src/types/onyx/Transaction';
7373
import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
7474
import {isEmptyObject} from '@src/types/utils/EmptyObject';
75+
import {changeTransactionsReport} from '../../src/libs/actions/Transaction';
7576
import * as InvoiceData from '../data/Invoice';
7677
import type {InvoiceTestData} from '../data/Invoice';
7778
import createRandomPolicy, {createCategoryTaxExpenseRules} from '../utils/collections/policies';
@@ -6134,4 +6135,115 @@ describe('actions/IOU', () => {
61346135
expect(updatedSnapshot?.data?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.receipt?.source).toBe(source);
61356136
});
61366137
});
6138+
6139+
describe('changeTransactionsReport', () => {
6140+
it('should set the correct optimistic onyx data for reporting a tracked expense', async () => {
6141+
let personalDetailsList: OnyxEntry<PersonalDetailsList>;
6142+
let expenseReport: OnyxEntry<Report>;
6143+
let transaction: OnyxEntry<Transaction>;
6144+
6145+
// Given a signed in account, which owns a workspace, and has a policy expense chat
6146+
Onyx.set(ONYXKEYS.SESSION, {email: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID});
6147+
const creatorPersonalDetails = personalDetailsList?.[CARLOS_ACCOUNT_ID] ?? {accountID: CARLOS_ACCOUNT_ID};
6148+
6149+
const policyID = generatePolicyID();
6150+
createWorkspace(CARLOS_EMAIL, true, "Carlos's Workspace", policyID);
6151+
createNewReport(creatorPersonalDetails, policyID);
6152+
// Create a tracked expense
6153+
const selfDMReport: Report = {
6154+
...createRandomReport(1),
6155+
reportID: '10',
6156+
chatType: CONST.REPORT.CHAT_TYPE.SELF_DM,
6157+
};
6158+
6159+
const amount = 100;
6160+
6161+
trackExpense({
6162+
report: selfDMReport,
6163+
isDraftPolicy: true,
6164+
action: CONST.IOU.ACTION.CREATE,
6165+
participantParams: {
6166+
payeeEmail: RORY_EMAIL,
6167+
payeeAccountID: RORY_ACCOUNT_ID,
6168+
participant: {accountID: RORY_ACCOUNT_ID},
6169+
},
6170+
transactionParams: {
6171+
amount,
6172+
currency: CONST.CURRENCY.USD,
6173+
created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING),
6174+
merchant: 'merchant',
6175+
billable: false,
6176+
},
6177+
});
6178+
await getOnyxData({
6179+
key: ONYXKEYS.COLLECTION.TRANSACTION,
6180+
waitForCollectionCallback: true,
6181+
callback: (allTransactions) => {
6182+
transaction = Object.values(allTransactions ?? {}).find((t) => !!t);
6183+
},
6184+
});
6185+
6186+
await getOnyxData({
6187+
key: ONYXKEYS.COLLECTION.REPORT,
6188+
waitForCollectionCallback: true,
6189+
callback: (allReports) => {
6190+
expenseReport = Object.values(allReports ?? {}).find((r) => r?.type === CONST.REPORT.TYPE.EXPENSE);
6191+
},
6192+
});
6193+
6194+
let iouReportActionOnSelfDMReport: OnyxEntry<ReportAction>;
6195+
let trackExpenseActionableWhisper: OnyxEntry<ReportAction>;
6196+
6197+
await getOnyxData({
6198+
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
6199+
waitForCollectionCallback: true,
6200+
callback: (allReportActions) => {
6201+
iouReportActionOnSelfDMReport = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`] ?? {}).find(
6202+
(r) => r?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
6203+
);
6204+
trackExpenseActionableWhisper = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport?.reportID}`] ?? {}).find(
6205+
(r) => r?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER,
6206+
);
6207+
},
6208+
});
6209+
6210+
expect(isMoneyRequestAction(iouReportActionOnSelfDMReport) ? getOriginalMessage(iouReportActionOnSelfDMReport)?.IOUTransactionID : undefined).toBe(transaction?.transactionID);
6211+
expect(trackExpenseActionableWhisper).toBeDefined();
6212+
6213+
if (!transaction || !expenseReport) {
6214+
return;
6215+
}
6216+
6217+
changeTransactionsReport([transaction?.transactionID], expenseReport?.reportID);
6218+
6219+
let updatedTransaction: OnyxEntry<Transaction>;
6220+
let updatedIOUReportActionOnSelfDMReport: OnyxEntry<ReportAction>;
6221+
let updatedTrackExpenseActionableWhisper: OnyxEntry<ReportAction>;
6222+
6223+
await getOnyxData({
6224+
key: ONYXKEYS.COLLECTION.TRANSACTION,
6225+
waitForCollectionCallback: true,
6226+
callback: (allTransactions) => {
6227+
updatedTransaction = Object.values(allTransactions ?? {}).find((t) => t?.transactionID === transaction?.transactionID);
6228+
},
6229+
});
6230+
6231+
await getOnyxData({
6232+
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
6233+
waitForCollectionCallback: true,
6234+
callback: (allReportActions) => {
6235+
updatedIOUReportActionOnSelfDMReport = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`] ?? {}).find(
6236+
(r) => r?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU,
6237+
);
6238+
updatedTrackExpenseActionableWhisper = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport?.reportID}`] ?? {}).find(
6239+
(r) => r?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER,
6240+
);
6241+
},
6242+
});
6243+
6244+
expect(updatedTransaction?.reportID).toBe(expenseReport?.reportID);
6245+
expect(isMoneyRequestAction(updatedIOUReportActionOnSelfDMReport) ? getOriginalMessage(updatedIOUReportActionOnSelfDMReport)?.IOUTransactionID : undefined).toBe(undefined);
6246+
expect(updatedTrackExpenseActionableWhisper).toBe(undefined);
6247+
});
6248+
});
61376249
});

0 commit comments

Comments
 (0)