@@ -16,7 +16,7 @@ import {getIOUActionForReportID} from './ReportActionsUtils';
1616import { findSelfDMReportID , getReportName , getReportOrDraftReport , getTransactionDetails } from './ReportUtils' ;
1717import type { TransactionDetails } from './ReportUtils' ;
1818import StringUtils from './StringUtils' ;
19- import { getAttendeesListDisplayString , getCurrency , getReimbursable , getWaypoints , isDistanceRequest , isManagedCardTransaction , isMerchantMissing } from './TransactionUtils' ;
19+ import { getAttendeesListDisplayString , getCurrency , getReimbursable , getWaypoints , isDistanceRequest , isExpenseSplit , isManagedCardTransaction , isMerchantMissing } from './TransactionUtils' ;
2020
2121const RECEIPT_SOURCE_URL = 'https://www.expensify.com/receipts/' ;
2222
@@ -190,9 +190,16 @@ function getMergeFields(targetTransaction: OnyxEntry<Transaction>) {
190190 * Get mergeableData data if one is missing, and conflict fields that need to be resolved by the user
191191 * @param targetTransaction - The target transaction
192192 * @param sourceTransaction - The source transaction
193+ * @param originalTargetTransaction - The original transaction of target transaction
194+ * @param localeCompare - The localize compare function
193195 * @returns mergeableData and conflictFields
194196 */
195- function getMergeableDataAndConflictFields ( targetTransaction : OnyxEntry < Transaction > , sourceTransaction : OnyxEntry < Transaction > , localeCompare : ( a : string , b : string ) => number ) {
197+ function getMergeableDataAndConflictFields (
198+ targetTransaction : OnyxEntry < Transaction > ,
199+ sourceTransaction : OnyxEntry < Transaction > ,
200+ originalTargetTransaction : OnyxEntry < Transaction > ,
201+ localeCompare : ( a : string , b : string ) => number ,
202+ ) {
196203 const conflictFields : string [ ] = [ ] ;
197204 const mergeableData : Record < string , unknown > = { } ;
198205
@@ -207,11 +214,16 @@ function getMergeableDataAndConflictFields(targetTransaction: OnyxEntry<Transact
207214 const isSourceValueEmpty = isEmptyMergeValue ( sourceValue ) ;
208215
209216 if ( field === 'amount' ) {
210- // If target transaction is a card transaction, always preserve the target transaction's amount and currency
217+ // If target transaction is a card or split expense, always preserve the target transaction's amount and currency
218+ // Card takes precedence over split expense
211219 // See https://github.com/Expensify/App/issues/68189#issuecomment-3167156907
212- if ( isManagedCardTransaction ( targetTransaction ) ) {
220+ const isTargetExpenseSplit = isExpenseSplit ( targetTransaction , originalTargetTransaction ) ;
221+ if ( isManagedCardTransaction ( targetTransaction ) || isTargetExpenseSplit ) {
213222 mergeableData [ field ] = targetValue ;
214223 mergeableData . currency = getCurrency ( targetTransaction ) ;
224+ if ( isTargetExpenseSplit ) {
225+ mergeableData . originalTransactionID = targetTransaction ?. comment ?. originalTransactionID ;
226+ }
215227 continue ;
216228 }
217229
@@ -346,24 +358,27 @@ function buildMergedTransactionData(targetTransaction: OnyxEntry<Transaction>, m
346358}
347359
348360/**
349- * Determines the correct target and source transaction IDs for merging based on transaction types.
361+ * Determines the correct target and source transactions for merging based on transaction types.
350362 *
351363 * Rules:
352- * - If one transaction is a card transaction, it becomes the target (card transactions take priority)
353- * - If both are cash transactions, the first parameter becomes the target
354- * - Users can only merge two cash expenses or one cash/one card expense
364+ * - The target transaction (transaction to keep) is selected based on the following priority: card transaction > split expense > cash transaction
355365 * - Users cannot merge two card expenses
366+ * - Users cannot merge two split expenses
367+ * - Users can merge any other combinations
356368 *
357- * @param targetTransaction - The first transaction in the merge operation
358- * @param sourceTransaction - The second transaction in the merge operation
359- * @returns An object containing the determined targetTransactionID and sourceTransactionID
369+ * @param targetTransaction - The transaction where the merge action is started from
370+ * @param sourceTransaction - The selected transaction to be merged with the target transaction
371+ * @param originalSourceTransaction - The original transaction of the source transaction
372+ * @returns An object containing the determined targetTransaction and sourceTransaction
360373 */
361- function selectTargetAndSourceTransactionsForMerge ( originalTargetTransaction : OnyxEntry < Transaction > , originalSourceTransaction : OnyxEntry < Transaction > ) {
362- if ( isManagedCardTransaction ( originalSourceTransaction ) ) {
363- return { targetTransaction : originalSourceTransaction , sourceTransaction : originalTargetTransaction } ;
374+ function selectTargetAndSourceTransactionsForMerge ( targetTransaction : OnyxEntry < Transaction > , sourceTransaction : OnyxEntry < Transaction > , originalSourceTransaction ?: OnyxEntry < Transaction > ) {
375+ // If target transaction is a card or split expense, always preserve the target transaction
376+ // Card takes precedence over split expense
377+ if ( isManagedCardTransaction ( sourceTransaction ) || ( isExpenseSplit ( sourceTransaction , originalSourceTransaction ) && ! isManagedCardTransaction ( targetTransaction ) ) ) {
378+ return { targetTransaction : sourceTransaction , sourceTransaction : targetTransaction } ;
364379 }
365380
366- return { targetTransaction : originalTargetTransaction , sourceTransaction : originalSourceTransaction } ;
381+ return { targetTransaction, sourceTransaction} ;
367382}
368383
369384/**
0 commit comments