Skip to content

Commit 0e2b131

Browse files
authored
Merge pull request Expensify#63648 from software-mansion-labs/korytko/fix-send-money-flow
[Better Expense Report View] Fix Send Money flow issue caused by excluding pay from one transaction thread logic
2 parents 5e91bd1 + c63bb04 commit 0e2b131

2 files changed

Lines changed: 98 additions & 3 deletions

File tree

src/libs/ReportActionsUtils.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,33 @@ function isTagModificationAction(actionName: string): boolean {
11841184
);
11851185
}
11861186

1187+
/**
1188+
* Used for Send Money flow, which is a special case where we have no IOU create action and only one IOU pay action.
1189+
* In other reports, pay actions do not count as a transactions, but this is an exception to this rule.
1190+
*/
1191+
function getSendMoneyFlowOneTransactionThreadID(actions: OnyxEntry<ReportActions> | ReportAction[], chatReportID?: string) {
1192+
if (!chatReportID) {
1193+
return undefined;
1194+
}
1195+
1196+
const iouActions = Object.values(actions ?? {}).filter(isMoneyRequestAction);
1197+
1198+
// sendMoneyFlow has only one IOU action...
1199+
if (iouActions.length !== 1) {
1200+
return undefined;
1201+
}
1202+
1203+
// ...which is 'pay'...
1204+
const isFirstActionPay = getOriginalMessage(iouActions.at(0))?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY;
1205+
1206+
const {type, chatType, parentReportID, parentReportActionID} = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`] ?? {};
1207+
1208+
// ...and can only be triggered on DM chats
1209+
const isDM = type === CONST.REPORT.TYPE.CHAT && !chatType && !(parentReportID && parentReportActionID);
1210+
1211+
return isFirstActionPay && isDM ? iouActions.at(0)?.childReportID : undefined;
1212+
}
1213+
11871214
/** Whether action has no linked report by design */
11881215
const isIOUActionTypeExcludedFromFiltering = (type: OriginalMessageIOU['type'] | undefined) =>
11891216
[CONST.IOU.REPORT_ACTION_TYPE.SPLIT, CONST.IOU.REPORT_ACTION_TYPE.TRACK, CONST.IOU.REPORT_ACTION_TYPE.PAY].some((actionType) => actionType === type);
@@ -1236,6 +1263,12 @@ function getOneTransactionThreadReportID(
12361263
return;
12371264
}
12381265

1266+
const sendMoneyFlowID = getSendMoneyFlowOneTransactionThreadID(reportActions, report?.chatReportID);
1267+
1268+
if (sendMoneyFlowID) {
1269+
return sendMoneyFlowID;
1270+
}
1271+
12391272
const iouRequestActions = [];
12401273
for (const action of reportActionsArray) {
12411274
// If the original message is a 'pay' IOU, it shouldn't be added to the transaction count.
@@ -2601,6 +2634,7 @@ export {
26012634
getWorkspaceDescriptionUpdatedMessage,
26022635
getWorkspaceReportFieldAddMessage,
26032636
getWorkspaceCustomUnitRateAddedMessage,
2637+
getSendMoneyFlowOneTransactionThreadID,
26042638
getWorkspaceTagUpdateMessage,
26052639
getWorkspaceReportFieldUpdateMessage,
26062640
getWorkspaceReportFieldDeleteMessage,

tests/unit/ReportActionsUtilsTest.ts

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import type {KeyValueMapping} from 'react-native-onyx';
22
import Onyx from 'react-native-onyx';
33
import {isExpenseReport} from '@libs/ReportUtils';
44
import {actionR14932 as mockIOUAction, originalMessageR14932 as mockOriginalMessage} from '../../__mocks__/reportData/actions';
5-
import {iouReportR14932 as mockIOUReport} from '../../__mocks__/reportData/reports';
5+
import {chatReportR14932 as mockChatReport, iouReportR14932 as mockIOUReport} from '../../__mocks__/reportData/reports';
66
import CONST from '../../src/CONST';
77
import * as ReportActionsUtils from '../../src/libs/ReportActionsUtils';
8-
import {getOneTransactionThreadReportID, getOriginalMessage, isIOUActionMatchingTransactionList} from '../../src/libs/ReportActionsUtils';
8+
import {getOneTransactionThreadReportID, getOriginalMessage, getSendMoneyFlowOneTransactionThreadID, isIOUActionMatchingTransactionList} from '../../src/libs/ReportActionsUtils';
99
import ONYXKEYS from '../../src/ONYXKEYS';
1010
import type {Report, ReportAction} from '../../src/types/onyx';
1111
import createRandomReport from '../utils/collections/reports';
@@ -856,7 +856,68 @@ describe('ReportActionsUtils', () => {
856856
});
857857
});
858858

859-
describe('getOneTransactionThreadReportID', () => {});
859+
describe('getSendMoneyFlowOneTransactionThreadID', () => {
860+
const mockChatReportID = 'REPORT';
861+
const mockDMChatReportID = 'REPORT_DM';
862+
const childReportID = 'childReport123';
863+
864+
const mockedReports: Record<`${typeof ONYXKEYS.COLLECTION.REPORT}${string}`, Report> = {
865+
[`${ONYXKEYS.COLLECTION.REPORT}${mockChatReportID}`]: {...mockChatReport, reportID: mockChatReportID},
866+
[`${ONYXKEYS.COLLECTION.REPORT}${mockDMChatReportID}`]: {
867+
...mockChatReport,
868+
reportID: mockDMChatReportID,
869+
chatType: undefined,
870+
parentReportID: undefined,
871+
parentReportActionID: undefined,
872+
},
873+
};
874+
875+
beforeEach(async () => {
876+
await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, mockedReports);
877+
});
878+
879+
const createAction = {
880+
...mockIOUAction,
881+
childReportID,
882+
originalMessage: {...getOriginalMessage(mockIOUAction), type: CONST.IOU.TYPE.CREATE},
883+
};
884+
885+
const nonIOUAction = {
886+
...mockIOUAction,
887+
childReportID,
888+
type: CONST.REPORT.ACTIONS.TYPE.CREATED,
889+
};
890+
891+
const payAction = {
892+
...mockIOUAction,
893+
childReportID,
894+
originalMessage: {...getOriginalMessage(mockIOUAction), type: CONST.IOU.TYPE.PAY},
895+
};
896+
897+
it('should return undefined for a single non-IOU action', () => {
898+
expect(getSendMoneyFlowOneTransactionThreadID([nonIOUAction], mockDMChatReportID)).toBeUndefined();
899+
});
900+
901+
it('should return undefined for multiple IOU actions regardless of type', () => {
902+
expect(getSendMoneyFlowOneTransactionThreadID([payAction, payAction], mockDMChatReportID)).toBeUndefined();
903+
});
904+
905+
it('should return undefined for a single IOU action that is not `Pay`', () => {
906+
expect(getSendMoneyFlowOneTransactionThreadID([createAction], mockDMChatReportID)).toBeUndefined();
907+
});
908+
909+
it('should return the appropriate childReportID for a valid single `Pay` IOU action in DM chat', () => {
910+
expect(getSendMoneyFlowOneTransactionThreadID([payAction], mockDMChatReportID)).toEqual(childReportID);
911+
});
912+
913+
it('should return undefined for a valid single `Pay` IOU action in a chat that is not DM', () => {
914+
expect(getSendMoneyFlowOneTransactionThreadID([payAction], mockChatReportID)).toBeUndefined();
915+
});
916+
917+
it('should return undefined for a valid `Pay` IOU action in DM chat that has also a create IOU action', () => {
918+
expect(getSendMoneyFlowOneTransactionThreadID([payAction, createAction], mockDMChatReportID)).toBeUndefined();
919+
});
920+
});
860921

861922
describe('shouldShowAddMissingDetails', () => {
862923
it('should return true if personal detail is not completed', async () => {

0 commit comments

Comments
 (0)