Skip to content

Commit 412e1a3

Browse files
authored
Merge pull request #96526 from FitseTLT/fix-similar-attendee-appearing-as-conflict-in-merge-flow
Fix - Merge expenses prompts for attendee selection even when both expenses have the same attendee
2 parents 0b5203e + e578070 commit 412e1a3

19 files changed

Lines changed: 85 additions & 65 deletions

src/hooks/useReceiptScanDrop.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ function useReceiptScanDrop() {
5858
parentReport: undefined,
5959
newIouRequestType: CONST.IOU.REQUEST_TYPE.SCAN,
6060
currentDate,
61-
currentUserPersonalDetails,
6261
hasOnlyPersonalPolicies,
6362
draftTransactionIDs,
6463
});
@@ -72,7 +71,6 @@ function useReceiptScanDrop() {
7271
? (initialTransaction as Partial<Transaction>)
7372
: buildOptimisticTransactionAndCreateDraft({
7473
initialTransaction: initialTransaction as Partial<Transaction>,
75-
currentUserPersonalDetails,
7674
reportID: newReportID,
7775
});
7876
const transactionID = transaction.transactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID;

src/hooks/useResetIOUType.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {validTransactionDraftIDsSelector} from '@selectors/TransactionDraft';
1717
import {useRef} from 'react';
1818
import {Keyboard} from 'react-native';
1919

20-
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
2120
import useDefaultParticipants from './useDefaultParticipants';
2221
import useOdometerDraftHydrator from './useOdometerDraftHydrator';
2322
import useOnyx from './useOnyx';
@@ -84,7 +83,6 @@ function useResetIOUType({
8483
const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
8584

8685
const personalPolicy = usePersonalPolicy();
87-
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
8886

8987
const hydrateOdometerOnLanding = useOdometerDraftHydrator({
9088
transaction,
@@ -135,7 +133,6 @@ function useResetIOUType({
135133
parentReport,
136134
currentDate,
137135
lastSelectedDistanceRates,
138-
currentUserPersonalDetails,
139136
hasOnlyPersonalPolicies: hasOnlyPersonalPolicies ?? true,
140137
draftTransactionIDs,
141138
defaultParticipants: isSelfDMDefault ? undefined : defaultParticipants,

src/hooks/useSearchBulkActions.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2207,7 +2207,19 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
22072207
text: translate('common.merge'),
22082208
icon: expensifyIcons.ArrowCollapse,
22092209
value: CONST.SEARCH.BULK_ACTION_TYPES.MERGE,
2210-
onSelected: () => setupMergeTransactionDataAndNavigate(transactionID, searchedTransactions, localeCompare, getCurrencyDecimals, reports, false, true),
2210+
onSelected: () =>
2211+
setupMergeTransactionDataAndNavigate(
2212+
transactionID,
2213+
searchedTransactions,
2214+
localeCompare,
2215+
getCurrencyDecimals,
2216+
reports,
2217+
false,
2218+
true,
2219+
undefined,
2220+
accountID,
2221+
personalDetails,
2222+
),
22112223
});
22122224
}
22132225
}

src/hooks/useSelectedTransactionsActions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ function useSelectedTransactionsActions({
128128
const {isBetaEnabled} = usePermissions();
129129
const {deleteTransactions, shouldOpenSplitExpenseEditFlowOnDelete} = useDeleteTransactions({report, reportActions, policy});
130130
const {login, accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
131+
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
131132
const defaultExpensePolicy = useDefaultExpensePolicy();
132133
const {isProduction} = useEnvironment();
133134

@@ -486,6 +487,8 @@ function useSelectedTransactionsActions({
486487
false,
487488
isOnSearch,
488489
selectedTransactionsList.length > 1 ? [policy, policy] : undefined,
490+
currentUserAccountID,
491+
personalDetails,
489492
),
490493
});
491494
}

src/libs/MergeTransactionUtils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,17 @@ function getMergeableDataAndConflictFields(
225225
searchReports: Array<OnyxEntry<Report>> = [],
226226
targetTransactionPolicy?: OnyxEntry<Policy>,
227227
sourceTransactionPolicy?: OnyxEntry<Policy>,
228+
targetReportOwnerAsAttendee?: Attendee,
229+
sourceReportOwnerAsAttendee?: Attendee,
228230
) {
229231
const conflictFields: string[] = [];
230232
const mergeableData: Record<string, unknown> = {};
231233

232-
const targetTransactionDetails = getTransactionDetails(targetTransaction);
233-
const sourceTransactionDetails = getTransactionDetails(sourceTransaction);
234+
// Resolve the report-owner fallback the same way the display path (buildMergeFieldsData) does, so an expense
235+
// with no stored attendee is compared as [owner] instead of [] and doesn't produce a false attendee conflict
236+
// against an expense whose attendee is that same owner.
237+
const targetTransactionDetails = getTransactionDetails(targetTransaction, undefined, undefined, undefined, undefined, targetReportOwnerAsAttendee);
238+
const sourceTransactionDetails = getTransactionDetails(sourceTransaction, undefined, undefined, undefined, undefined, sourceReportOwnerAsAttendee);
234239

235240
for (const field of getMergeFields(targetTransaction)) {
236241
const targetValue = getMergeFieldValue(targetTransactionDetails, targetTransaction, field);

src/libs/actions/IOU/MoneyRequest.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {WRITE_COMMANDS} from '@libs/API/types';
66
import DateUtils from '@libs/DateUtils';
77
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
88
import {getGPSRoutes, getGPSWaypoints} from '@libs/GPSDraftDetailsUtils';
9-
import {formatCurrentUserToAttendee, getExistingTransactionID} from '@libs/IOUUtils';
9+
import {getExistingTransactionID} from '@libs/IOUUtils';
1010
import Log from '@libs/Log';
1111
import Navigation from '@libs/Navigation/Navigation';
1212
import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils';
@@ -57,7 +57,6 @@ import type {
5757
} from '@src/types/onyx';
5858
import type {ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues';
5959
import type {Accountant, Attendee, Participant} from '@src/types/onyx/IOU';
60-
import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails';
6160
import type {Unit} from '@src/types/onyx/Policy';
6261
import type {Comment, Receipt} from '@src/types/onyx/Transaction';
6362
import {isEmptyObject} from '@src/types/utils/EmptyObject';
@@ -277,7 +276,6 @@ type InitMoneyRequestParams = {
277276
parentReport: OnyxEntry<Report>;
278277
currentDate: string | undefined;
279278
lastSelectedDistanceRates?: OnyxEntry<LastSelectedDistanceRates>;
280-
currentUserPersonalDetails: CurrentUserPersonalDetails;
281279
isTrackDistanceExpense?: boolean;
282280
hasOnlyPersonalPolicies: boolean;
283281
draftTransactionIDs?: string[];
@@ -308,7 +306,6 @@ function initMoneyRequest({
308306
parentReport,
309307
currentDate,
310308
lastSelectedDistanceRates,
311-
currentUserPersonalDetails,
312309
hasOnlyPersonalPolicies,
313310
draftTransactionIDs,
314311
defaultParticipants,
@@ -337,9 +334,7 @@ function initMoneyRequest({
337334
return;
338335
}
339336

340-
const comment: Comment = {
341-
attendees: formatCurrentUserToAttendee(currentUserPersonalDetails),
342-
};
337+
const comment: Comment = {};
343338
let requestCategory: string | null = null;
344339

345340
// Set up initial distance expense state

src/libs/actions/MergeTransaction.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ import {
3131
} from '@libs/ReportUtils';
3232

3333
import CONST from '@src/CONST';
34-
import {isDistanceRequest, isTransactionPendingDelete} from '@src/libs/TransactionUtils';
34+
import {getReportOwnerAccountIDAsAttendee, getReportOwnerAsAttendee, isDistanceRequest, isTransactionPendingDelete} from '@src/libs/TransactionUtils';
3535
import ONYXKEYS from '@src/ONYXKEYS';
3636
import {DYNAMIC_ROUTES} from '@src/ROUTES';
37-
import type {CardList, MergeTransaction, Policy, PolicyCategories, PolicyTagLists, Report, ReportActions, Transaction, TransactionViolations} from '@src/types/onyx';
37+
import type {CardList, MergeTransaction, PersonalDetailsList, Policy, PolicyCategories, PolicyTagLists, Report, ReportActions, Transaction, TransactionViolations} from '@src/types/onyx';
38+
import type {Attendee} from '@src/types/onyx/IOU';
3839

3940
import type {OnyxCollection, OnyxEntry, OnyxMergeInput, OnyxUpdate} from 'react-native-onyx';
4041

@@ -47,6 +48,20 @@ import {getCleanUpTransactionThreadReportOnyxData} from './IOU/DeleteMoneyReques
4748
import {getDeleteTrackExpenseInformation} from './IOU/TrackExpense';
4849
import {getUpdateMoneyRequestParams, getUpdateTrackExpenseParams} from './IOU/UpdateMoneyRequest';
4950

51+
/**
52+
* Resolve a transaction's report owner as a default attendee, mirroring the `useReportOwnerAsAttendee` hook so the
53+
* non-React merge entry point can apply the same empty-attendee fallback the details/receipt pages use. The Onyx data
54+
* is passed in by the caller rather than read from a module subscription.
55+
*/
56+
function getReportOwnerAsAttendeeForTransaction(
57+
transaction: OnyxEntry<Transaction>,
58+
currentUserAccountID: number | undefined,
59+
personalDetails: OnyxEntry<PersonalDetailsList>,
60+
): Attendee | undefined {
61+
const ownerAccountID = getReportOwnerAccountIDAsAttendee(transaction, currentUserAccountID);
62+
return getReportOwnerAsAttendee(ownerAccountID ? (personalDetails?.[ownerAccountID] ?? undefined) : undefined);
63+
}
64+
5065
/**
5166
* Setup merge transaction data for merging flow
5267
*/
@@ -91,6 +106,8 @@ function setupMergeTransactionDataAndNavigate(
91106
isSelectingSourceTransaction?: boolean,
92107
isOnSearch?: boolean,
93108
policies?: Array<OnyxEntry<Policy>>,
109+
currentUserAccountID?: number,
110+
personalDetails?: OnyxEntry<PersonalDetailsList>,
94111
) {
95112
if (!transactions.length || transactions.length > 2) {
96113
return;
@@ -139,6 +156,8 @@ function setupMergeTransactionDataAndNavigate(
139156
searchReports,
140157
targetTransactionPolicy,
141158
sourceTransactionPolicy,
159+
getReportOwnerAsAttendeeForTransaction(targetTransaction, currentUserAccountID, personalDetails),
160+
getReportOwnerAsAttendeeForTransaction(sourceTransaction, currentUserAccountID, personalDetails),
142161
);
143162
if (!conflictFields.length) {
144163
// If there are no conflict fields, we should set mergeable data and navigate to the confirmation page

src/libs/actions/TransactionEdit.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import {formatCurrentUserToAttendee} from '@libs/IOUUtils';
21
import revokeOdometerImageUri from '@libs/OdometerUtils';
32

43
import CONST from '@src/CONST';
54
import ONYXKEYS from '@src/ONYXKEYS';
6-
import type {PersonalDetails, Transaction} from '@src/types/onyx';
5+
import type {Transaction} from '@src/types/onyx';
76

87
import type {Connection, OnyxEntry} from 'react-native-onyx';
98

@@ -170,18 +169,16 @@ function removeTransactionReceipt(transactionID: string | undefined) {
170169

171170
type BuildOptimisticTransactionParams = {
172171
initialTransaction: Partial<Transaction>;
173-
currentUserPersonalDetails: PersonalDetails;
174172
reportID: string;
175173
};
176174

177-
function buildOptimisticTransactionAndCreateDraft({initialTransaction, currentUserPersonalDetails, reportID}: BuildOptimisticTransactionParams): Transaction {
175+
function buildOptimisticTransactionAndCreateDraft({initialTransaction, reportID}: BuildOptimisticTransactionParams): Transaction {
178176
const newTransactionID = generateTransactionID();
179177
const {currency, iouRequestType, isFromGlobalCreate, isFromFloatingActionButton} = initialTransaction ?? {};
180178
const newTransaction = {
181179
amount: 0,
182180
created: format(new Date(), 'yyyy-MM-dd'),
183181
currency,
184-
comment: {attendees: formatCurrentUserToAttendee(currentUserPersonalDetails)},
185182
iouRequestType,
186183
reportID,
187184
transactionID: newTransactionID,

src/pages/Share/SubmitDetailsPage.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,13 @@ function SubmitDetailsPage({
173173
report,
174174
parentReport,
175175
currentDate,
176-
currentUserPersonalDetails,
177176
hasOnlyPersonalPolicies,
178177
draftTransactionIDs,
179178
});
180179
// Populate transaction.participants so IOURequestStepReport can highlight the destination (mirrors other expense flows).
181180
setMoneyRequestParticipantsFromReport(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report, currentUserPersonalDetails.accountID);
182181
// eslint-disable-next-line react-hooks/exhaustive-deps
183-
}, [reportOrAccountID, policy, personalPolicy, report, parentReport, currentDate, currentUserPersonalDetails, hasOnlyPersonalPolicies]);
182+
}, [reportOrAccountID, policy, personalPolicy, report, parentReport, currentDate, currentUserPersonalDetails.accountID, hasOnlyPersonalPolicies]);
184183

185184
// Use the branch-aware values computed above: for a share that needs conversion (e.g. HEIC), these resolve to the
186185
// converted JPEG from VALIDATED_FILE_OBJECT; otherwise they fall back to the raw attachment. Re-deriving from

src/pages/TransactionMerge/DynamicDetailsReviewPage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ function DynamicDetailsReviewPage({route}: DynamicDetailsReviewPageProps) {
7777
[targetTransactionReport, sourceTransactionReport],
7878
targetTransactionPolicy,
7979
sourceTransactionPolicy,
80+
targetReportOwnerAsAttendee,
81+
sourceReportOwnerAsAttendee,
8082
);
8183

8284
setMergeTransactionKey(transactionID, mergeableData);
@@ -91,6 +93,8 @@ function DynamicDetailsReviewPage({route}: DynamicDetailsReviewPageProps) {
9193
targetTransactionPolicy,
9294
sourceTransactionPolicy,
9395
getCurrencyDecimals,
96+
targetReportOwnerAsAttendee,
97+
sourceReportOwnerAsAttendee,
9498
]);
9599

96100
// Handle selection

0 commit comments

Comments
 (0)