Skip to content

Commit a55cb8c

Browse files
authored
Merge pull request Expensify#68536 from callstack-internal/VickyStash/refactor/67886-several-transactions-expense-inbox
Update several transactions expense in the chat to work as expected if no optimistic thread report was created
2 parents c300cbf + 01af311 commit a55cb8c

10 files changed

Lines changed: 169 additions & 33 deletions

File tree

src/ROUTES.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,14 @@ const ROUTES = {
100100
backTo,
101101
moneyRequestReportActionID,
102102
transactionID,
103+
iouReportID,
103104
}: {
104105
reportID: string | undefined;
105106
reportActionID?: string;
106107
backTo?: string;
107108
moneyRequestReportActionID?: string;
108109
transactionID?: string;
110+
iouReportID?: string;
109111
}) => {
110112
if (!reportID) {
111113
Log.warn('Invalid reportID is used to build the SEARCH_REPORT route');
@@ -121,6 +123,9 @@ const ROUTES = {
121123
if (moneyRequestReportActionID) {
122124
queryParams.push(`moneyRequestReportActionID=${moneyRequestReportActionID}`);
123125
}
126+
if (iouReportID) {
127+
queryParams.push(`iouReportID=${iouReportID}`);
128+
}
124129

125130
const queryString = queryParams.length > 0 ? (`${baseRoute}?${queryParams.join('&')}` as const) : baseRoute;
126131
return getUrlWithBackToParam(queryString, backTo);

src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,19 @@ import {setActiveTransactionThreadIDs} from '@libs/actions/TransactionThreadNavi
2222
import {convertToDisplayString} from '@libs/CurrencyUtils';
2323
import {getThreadReportIDsForTransactions} from '@libs/MoneyRequestReportUtils';
2424
import {navigationRef} from '@libs/Navigation/Navigation';
25-
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
26-
import {getMoneyRequestSpendBreakdown} from '@libs/ReportUtils';
25+
import {getIOUActionForTransactionID, getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
26+
import {generateReportID, getMoneyRequestSpendBreakdown} from '@libs/ReportUtils';
2727
import {compareValues, getColumnsToShow, isTransactionAmountTooLong, isTransactionTaxAmountTooLong} from '@libs/SearchUIUtils';
2828
import {getTransactionPendingAction, isTransactionPendingDelete} from '@libs/TransactionUtils';
2929
import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear';
3030
import Navigation from '@navigation/Navigation';
31+
import type {ReportsSplitNavigatorParamList} from '@navigation/types';
3132
import variables from '@styles/variables';
3233
import CONST from '@src/CONST';
3334
import type {TranslationPaths} from '@src/languages/types';
3435
import NAVIGATORS from '@src/NAVIGATORS';
3536
import ROUTES from '@src/ROUTES';
37+
import type SCREENS from '@src/SCREENS';
3638
import type * as OnyxTypes from '@src/types/onyx';
3739
import MoneyRequestReportTableHeader from './MoneyRequestReportTableHeader';
3840
import MoneyRequestReportTotalSpend from './MoneyRequestReportTotalSpend';
@@ -74,6 +76,8 @@ const sortableColumnNames = [
7476
CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT,
7577
];
7678

79+
type ReportScreenNavigationProps = ReportsSplitNavigatorParamList[typeof SCREENS.REPORT];
80+
7781
type SortableColumnName = TupleToUnion<typeof sortableColumnNames>;
7882

7983
type SortedTransactions = {
@@ -167,21 +171,31 @@ function MoneyRequestReportTransactionList({
167171
return (Object.keys(columns) as SearchColumnType[]).filter((column) => columns[column]);
168172
}, [transactions, session?.accountID]);
169173

174+
/**
175+
* Navigate to the transaction thread for a transaction, creating one optimistically if it doesn't yet exist.
176+
*/
170177
const navigateToTransaction = useCallback(
171178
(activeTransactionID: string) => {
172179
const iouAction = getIOUActionForTransactionID(reportActions, activeTransactionID);
173-
const reportIDToNavigate = iouAction?.childReportID;
174-
if (!reportIDToNavigate) {
175-
return;
176-
}
177-
178180
const backTo = Navigation.getActiveRoute();
181+
const reportIDToNavigate = iouAction?.childReportID ?? generateReportID();
182+
183+
const routeParams = {
184+
reportID: reportIDToNavigate,
185+
backTo,
186+
} as ReportScreenNavigationProps;
187+
188+
if (!iouAction?.childReportID) {
189+
routeParams.moneyRequestReportActionID = iouAction?.reportActionID;
190+
routeParams.transactionID = activeTransactionID;
191+
routeParams.iouReportID = isMoneyRequestAction(iouAction) ? getOriginalMessage(iouAction)?.IOUReportID : undefined;
192+
}
179193

180194
// Single transaction report will open in RHP, and we need to find every other report ID for the rest of transactions
181195
// to display prev/next arrows in RHP for navigation
182196
const sortedSiblingTransactionReportIDs = getThreadReportIDsForTransactions(reportActions, sortedTransactions);
183197
setActiveTransactionThreadIDs(sortedSiblingTransactionReportIDs).then(() => {
184-
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: reportIDToNavigate, backTo}));
198+
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(routeParams));
185199
});
186200
},
187201
[reportActions, sortedTransactions],

src/components/TransactionItemRow/DataCells/ChatBubbleCell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {Report} from '@src/types/onyx';
1818
import type Transaction from '@src/types/onyx/Transaction';
1919

2020
const isReportUnread = ({lastReadTime = '', lastVisibleActionCreated = '', lastMentionedTime = ''}: Report): boolean =>
21-
lastReadTime < lastVisibleActionCreated || lastReadTime < (lastMentionedTime ?? '');
21+
lastReadTime <= lastVisibleActionCreated || lastReadTime < (lastMentionedTime ?? '');
2222

2323
function ChatBubbleCell({transaction, containerStyles, isInSingleTransactionReport}: {transaction: Transaction; containerStyles?: ViewStyle[]; isInSingleTransactionReport?: boolean}) {
2424
const theme = useTheme();

src/libs/API/parameters/MergeDuplicatesParams.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type MergeDuplicatesParams = {
1313
receiptID: number;
1414
reportID: string | undefined;
1515
reportActionID?: string | undefined;
16+
transactionThreadReportID?: string;
17+
createdReportActionIDForThread?: string;
1618
};
1719

1820
export default MergeDuplicatesParams;

src/libs/Navigation/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,7 @@ type ReportsSplitNavigatorParamList = {
18551855
backTo?: Routes;
18561856
moneyRequestReportActionID?: string;
18571857
transactionID?: string;
1858+
iouReportID?: string;
18581859
};
18591860
[SCREENS.ATTACHMENTS]: AttachmentModalScreenParams;
18601861
};

src/libs/Permissions.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ function isBetaEnabled(beta: Beta, betas: OnyxEntry<Beta[]>): boolean {
2626
return false;
2727
}
2828

29-
if (beta === CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS) {
30-
return false;
31-
}
32-
3329
return !!betas?.includes(beta) || canUseAllBetas(betas);
3430
}
3531

src/libs/actions/IOU.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11237,9 +11237,6 @@ function putOnHold(transactionID: string, comment: string, initialReportID: stri
1123711237
function putTransactionsOnHold(transactionsID: string[], comment: string, reportID: string) {
1123811238
transactionsID.forEach((transactionID) => {
1123911239
const {childReportID} = getIOUActionForReportID(reportID, transactionID) ?? {};
11240-
if (!childReportID) {
11241-
return;
11242-
}
1124311240
putOnHold(transactionID, comment, childReportID);
1124411241
});
1124511242
}
@@ -11449,7 +11446,9 @@ function getIOUActionForTransactions(transactionIDList: Array<string | undefined
1144911446
}
1145011447

1145111448
/** Merge several transactions into one by updating the fields of the one we want to keep and deleting the rest */
11452-
function mergeDuplicates(params: MergeDuplicatesParams) {
11449+
function mergeDuplicates({transactionThreadReportID: optimisticTransactionThreadReportID, ...params}: MergeDuplicatesParams) {
11450+
const allParams: MergeDuplicatesParams = {...params};
11451+
1145311452
const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`];
1145411453

1145511454
const optimisticTransactionData: OnyxUpdate = {
@@ -11580,7 +11579,8 @@ function mergeDuplicates(params: MergeDuplicatesParams) {
1158011579

1158111580
const optimisticReportAction = buildOptimisticResolvedDuplicatesReportAction();
1158211581

11583-
const transactionThreadReportID = params.reportID ? getIOUActionForTransactions([params.transactionID], params.reportID).at(0)?.childReportID : undefined;
11582+
const transactionThreadReportID =
11583+
optimisticTransactionThreadReportID ?? (params.reportID ? getIOUActionForTransactions([params.transactionID], params.reportID).at(0)?.childReportID : undefined);
1158411584
const optimisticReportActionData: OnyxUpdate = {
1158511585
onyxMethod: Onyx.METHOD.MERGE,
1158611586
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`,
@@ -11599,6 +11599,7 @@ function mergeDuplicates(params: MergeDuplicatesParams) {
1159911599

1160011600
const optimisticData: OnyxUpdate[] = [];
1160111601
const failureData: OnyxUpdate[] = [];
11602+
const successData: OnyxUpdate[] = [];
1160211603

1160311604
optimisticData.push(
1160411605
optimisticTransactionData,
@@ -11617,7 +11618,64 @@ function mergeDuplicates(params: MergeDuplicatesParams) {
1161711618
failureReportActionData,
1161811619
);
1161911620

11620-
API.write(WRITE_COMMANDS.MERGE_DUPLICATES, {...params, reportActionID: optimisticReportAction.reportActionID}, {optimisticData, failureData});
11621+
if (optimisticTransactionThreadReportID) {
11622+
const iouAction = getIOUActionForReportID(params.reportID, params.transactionID);
11623+
const optimisticCreatedAction = buildOptimisticCreatedReportAction(currentUserEmail);
11624+
const optimisticTransactionThreadReport = buildTransactionThread(iouAction, expenseReport, undefined, optimisticTransactionThreadReportID);
11625+
11626+
allParams.transactionThreadReportID = optimisticTransactionThreadReportID;
11627+
allParams.createdReportActionIDForThread = optimisticCreatedAction?.reportActionID;
11628+
optimisticTransactionThreadReport.pendingFields = {
11629+
createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
11630+
};
11631+
11632+
optimisticData.push(
11633+
{
11634+
onyxMethod: Onyx.METHOD.SET,
11635+
key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThreadReportID}`,
11636+
value: optimisticTransactionThreadReport,
11637+
},
11638+
{
11639+
onyxMethod: Onyx.METHOD.SET,
11640+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThreadReportID}`,
11641+
value: {[optimisticCreatedAction.reportActionID]: optimisticCreatedAction},
11642+
},
11643+
);
11644+
11645+
failureData.push(
11646+
{
11647+
onyxMethod: Onyx.METHOD.SET,
11648+
key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThreadReportID}`,
11649+
value: null,
11650+
},
11651+
{
11652+
onyxMethod: Onyx.METHOD.SET,
11653+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThreadReportID}`,
11654+
value: null,
11655+
},
11656+
);
11657+
11658+
if (iouAction?.reportActionID) {
11659+
optimisticData.push({
11660+
onyxMethod: Onyx.METHOD.MERGE,
11661+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThreadReport?.parentReportID}`,
11662+
value: {[iouAction?.reportActionID]: {childReportID: optimisticTransactionThreadReportID, childType: CONST.REPORT.TYPE.CHAT}},
11663+
});
11664+
failureData.push({
11665+
onyxMethod: Onyx.METHOD.MERGE,
11666+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThreadReport?.parentReportID}`,
11667+
value: {[iouAction?.reportActionID]: {childReportID: null, childType: null}},
11668+
});
11669+
}
11670+
11671+
successData.push({
11672+
onyxMethod: Onyx.METHOD.MERGE,
11673+
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThreadReportID}`,
11674+
value: {[optimisticCreatedAction.reportActionID]: {pendingAction: null}},
11675+
});
11676+
}
11677+
11678+
API.write(WRITE_COMMANDS.MERGE_DUPLICATES, {...allParams, reportActionID: optimisticReportAction.reportActionID}, {optimisticData, failureData, successData});
1162111679
}
1162211680

1162311681
function updateLastLocationPermissionPrompt() {

src/pages/TransactionDuplicate/Confirmation.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import CONST from '@src/CONST';
2626
import * as IOU from '@src/libs/actions/IOU';
2727
import * as ReportActionsUtils from '@src/libs/ReportActionsUtils';
2828
import * as ReportUtils from '@src/libs/ReportUtils';
29+
import {generateReportID} from '@src/libs/ReportUtils';
2930
import * as TransactionUtils from '@src/libs/TransactionUtils';
3031
import {getTransactionID} from '@src/libs/TransactionUtils';
3132
import ONYXKEYS from '@src/ONYXKEYS';
@@ -84,8 +85,9 @@ function Confirmation() {
8485
const isReportOwner = iouReport?.ownerAccountID === currentUserPersonalDetails?.accountID;
8586

8687
const mergeDuplicates = useCallback(() => {
88+
const transactionThreadReportID = reportAction?.childReportID ?? generateReportID();
8789
if (!reportAction?.childReportID) {
88-
return;
90+
transactionsMergeParams.transactionThreadReportID = transactionThreadReportID;
8991
}
9092
IOU.mergeDuplicates(transactionsMergeParams);
9193
Navigation.dismissModal();

src/pages/TransactionMerge/DetailsReviewPage.tsx

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect, useState} from 'react';
1+
import React, {useCallback, useEffect, useState} from 'react';
22
import {View} from 'react-native';
33
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
44
import Button from '@components/Button';
@@ -27,6 +27,8 @@ import type {MergeFieldKey, MergeValue} from '@libs/MergeTransactionUtils';
2727
import Navigation from '@libs/Navigation/Navigation';
2828
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
2929
import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
30+
import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
31+
import {buildTransactionThread, generateReportID, getReportOrDraftReport} from '@libs/ReportUtils';
3032
import {getCurrency} from '@libs/TransactionUtils';
3133
import {openReport} from '@userActions/Report';
3234
import type {TranslationPaths} from '@src/languages/types';
@@ -44,16 +46,33 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
4446
const styles = useThemeStyles();
4547
const {transactionID, backTo} = route.params;
4648

47-
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
4849
const [mergeTransaction, mergeTransactionMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${transactionID}`, {canBeMissing: false});
4950
const [targetTransaction = getTargetTransactionFromMergeTransaction(mergeTransaction)] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${mergeTransaction?.targetTransactionID}`, {
5051
canBeMissing: true,
5152
});
52-
const [sourceTransaction = getSourceTransactionFromMergeTransaction(mergeTransaction)] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${mergeTransaction?.sourceTransactionID}`, {
53+
const [hasOnceLoadedTransactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${targetTransaction?.reportID}`, {
54+
selector: (value) => value?.hasOnceLoadedReportActions,
5355
canBeMissing: true,
5456
});
5557
const targetTransactionThreadReportID = getTransactionThreadReportID(targetTransaction);
56-
const targetTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`];
58+
const [iouActionForTargetTransaction] = useOnyx(
59+
`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetTransaction?.reportID}`,
60+
{
61+
selector: (value) => {
62+
if (!hasOnceLoadedTransactionThreadReportActions || !!targetTransactionThreadReportID || !targetTransaction?.transactionID) {
63+
return undefined;
64+
}
65+
return getIOUActionForTransactionID(Object.values(value ?? {}), targetTransaction?.transactionID);
66+
},
67+
canBeMissing: true,
68+
},
69+
[hasOnceLoadedTransactionThreadReportActions, targetTransactionThreadReportID, targetTransaction?.transactionID],
70+
);
71+
const [sourceTransaction = getSourceTransactionFromMergeTransaction(mergeTransaction)] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${mergeTransaction?.sourceTransactionID}`, {
72+
canBeMissing: true,
73+
});
74+
const [targetTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`, {canBeMissing: true});
75+
const [currentUserEmail] = useOnyx(ONYXKEYS.SESSION, {selector: (value) => value?.email, canBeMissing: false});
5776

5877
const [hasErrors, setHasErrors] = useState<Partial<Record<MergeFieldKey, boolean>>>({});
5978
const [diffFields, setDiffFields] = useState<MergeFieldKey[]>([]);
@@ -70,6 +89,24 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
7089
setDiffFields(conflictFields as MergeFieldKey[]);
7190
}, [targetTransaction, sourceTransaction, transactionID]);
7291

92+
const createTargetTransactionThreadReport = useCallback(() => {
93+
const transactionThreadReportID = generateReportID();
94+
const iouReport = getReportOrDraftReport(targetTransaction?.reportID);
95+
const optimisticTransactionThread = buildTransactionThread(iouActionForTargetTransaction, iouReport, undefined, transactionThreadReportID);
96+
97+
openReport(
98+
transactionThreadReportID,
99+
undefined,
100+
currentUserEmail ? [currentUserEmail] : [],
101+
optimisticTransactionThread,
102+
iouActionForTargetTransaction?.reportActionID,
103+
false,
104+
[],
105+
undefined,
106+
targetTransaction?.transactionID,
107+
);
108+
}, [currentUserEmail, iouActionForTargetTransaction, targetTransaction?.reportID, targetTransaction?.transactionID]);
109+
73110
useEffect(() => {
74111
if (!isCheckingDataBeforeGoNext) {
75112
return;
@@ -79,6 +116,14 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
79116
// The App may not have the transaction thread report loaded for card transactions, so we need to trigger
80117
// OpenReport to ensure the transaction thread report is available for confirmation page
81118
if (!targetTransactionThreadReportID && targetTransaction?.reportID) {
119+
// If the report was already loaded before, but there are still no transaction thread report info, it means it hasn't been created yet.
120+
// So we should create it.
121+
if (hasOnceLoadedTransactionThreadReportActions) {
122+
createTargetTransactionThreadReport();
123+
setIsCheckingDataBeforeGoNext(false);
124+
Navigation.navigate(ROUTES.MERGE_TRANSACTION_CONFIRMATION_PAGE.getRoute(transactionID, Navigation.getActiveRoute()));
125+
return;
126+
}
82127
return openReport(targetTransaction.reportID);
83128
}
84129
if (targetTransactionThreadReportID && !targetTransactionThreadReport) {
@@ -91,7 +136,18 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
91136

92137
Navigation.navigate(ROUTES.MERGE_TRANSACTION_CONFIRMATION_PAGE.getRoute(transactionID, Navigation.getActiveRoute()));
93138
setIsCheckingDataBeforeGoNext(false);
94-
}, [isCheckingDataBeforeGoNext, targetTransactionThreadReportID, targetTransaction?.reportID, targetTransactionThreadReport, transactionID]);
139+
}, [
140+
isCheckingDataBeforeGoNext,
141+
targetTransactionThreadReportID,
142+
targetTransaction?.reportID,
143+
targetTransactionThreadReport,
144+
transactionID,
145+
hasOnceLoadedTransactionThreadReportActions,
146+
iouActionForTargetTransaction,
147+
currentUserEmail,
148+
targetTransaction?.transactionID,
149+
createTargetTransactionThreadReport,
150+
]);
95151

96152
// Handle selection
97153
const handleSelect = (field: MergeFieldKey, value: MergeValue) => {

0 commit comments

Comments
 (0)