From 8677040effcaa0670b940ea7608bfe3225cd41a6 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 25 Sep 2025 05:08:15 +0700
Subject: [PATCH 01/54] display tax rate in merge details page

---
 src/libs/MergeTransactionUtils.ts             | 21 +++++++++++++------
 .../TransactionMerge/DetailsReviewPage.tsx    |  8 +++++--
 src/types/onyx/MergeTransaction.ts            |  3 +++
 tests/unit/MergeTransactionUtilsTest.ts       | 16 +++++++-------
 4 files changed, 32 insertions(+), 16 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index d731a51b400d..53c80495ec39 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -3,7 +3,7 @@ import type {TupleToUnion} from 'type-fest';
 import type {LocaleContextProps} from '@components/LocaleContextProvider';
 import CONST from '@src/CONST';
 import type {TranslationPaths} from '@src/languages/types';
-import type {MergeTransaction, Transaction} from '@src/types/onyx';
+import type {MergeTransaction, Policy, Transaction} from '@src/types/onyx';
 import type {Receipt} from '@src/types/onyx/Transaction';
 import {convertToDisplayString} from './CurrencyUtils';
 import getReceiptFilenameFromTransaction from './getReceiptFilenameFromTransaction';
@@ -13,12 +13,12 @@ import {getIOUActionForReportID} from './ReportActionsUtils';
 import {findSelfDMReportID, getReportName, getReportOrDraftReport, getTransactionDetails} from './ReportUtils';
 import type {TransactionDetails} from './ReportUtils';
 import StringUtils from './StringUtils';
-import {getCurrency, getReimbursable, isCardTransaction, isMerchantMissing} from './TransactionUtils';
+import {getCurrency, getReimbursable, getTaxName, isCardTransaction, isMerchantMissing} from './TransactionUtils';
 
 const RECEIPT_SOURCE_URL = 'https://www.expensify.com/receipts/';
 
 // Define the specific merge fields we want to handle
-const MERGE_FIELDS = ['amount', 'currency', 'merchant', 'created', 'category', 'tag', 'description', 'reimbursable', 'billable', 'reportID'] as const;
+const MERGE_FIELDS = ['amount', 'currency', 'merchant', 'created', 'category', 'tag', 'description', 'taxValue', 'reimbursable', 'billable', 'reportID'] as const;
 type MergeFieldKey = TupleToUnion<typeof MERGE_FIELDS>;
 type MergeFieldOption = {
     transaction: Transaction;
@@ -43,6 +43,7 @@ const MERGE_FIELD_TRANSLATION_KEYS = {
     billable: 'common.billable',
     created: 'common.date',
     reportID: 'common.report',
+    taxValue: 'iou.taxRate',
 } as const;
 
 // Get the filename from the receipt
@@ -144,6 +145,9 @@ function getMergeFieldValue(transactionDetails: TransactionDetails | undefined,
     if (field === 'merchant' && isMerchantMissing(transaction)) {
         return '';
     }
+    if (field === 'taxValue') {
+        return transaction.taxValue;
+    }
 
     return transactionDetails[field] ?? '';
 }
@@ -328,7 +332,7 @@ function selectTargetAndSourceTransactionsForMerge(originalTargetTransaction: On
  * @param translate - The translation function
  * @returns The formatted display string for the field value
  */
-function getDisplayValue(field: MergeFieldKey, transaction: Transaction, translate: LocaleContextProps['translate']): string {
+function getDisplayValue(field: MergeFieldKey, transaction: Transaction, policy: Policy | undefined, translate: LocaleContextProps['translate']): string {
     const fieldValue = getMergeFieldValue(getTransactionDetails(transaction), transaction, field);
 
     if (isEmptyMergeValue(fieldValue)) {
@@ -349,6 +353,9 @@ function getDisplayValue(field: MergeFieldKey, transaction: Transaction, transla
     if (field === 'reportID') {
         return fieldValue === CONST.REPORT.UNREPORTED_REPORT_ID ? translate('common.none') : getReportName(getReportOrDraftReport(fieldValue.toString()));
     }
+    if (field === 'taxValue') {
+        return getTaxName(policy, transaction) ?? '';
+    }
     return String(fieldValue);
 }
 /**
@@ -365,6 +372,8 @@ function buildMergeFieldsData(
     targetTransaction: Transaction | undefined,
     sourceTransaction: Transaction | undefined,
     mergeTransaction: MergeTransaction | null | undefined,
+    targetTransactionPolicy: Policy | undefined,
+    sourceTransactionPolicy: Policy | undefined,
     translate: LocaleContextProps['translate'],
 ): MergeFieldData[] {
     if (!targetTransaction || !sourceTransaction) {
@@ -379,12 +388,12 @@ function buildMergeFieldsData(
         const options: MergeFieldOption[] = [
             {
                 transaction: targetTransaction,
-                displayValue: getDisplayValue(field, targetTransaction, translate),
+                displayValue: getDisplayValue(field, targetTransaction, targetTransactionPolicy, translate),
                 isSelected: selectedTransactionId === targetTransaction.transactionID,
             },
             {
                 transaction: sourceTransaction,
-                displayValue: getDisplayValue(field, sourceTransaction, translate),
+                displayValue: getDisplayValue(field, sourceTransaction, sourceTransactionPolicy, translate),
                 isSelected: selectedTransactionId === sourceTransaction.transactionID,
             },
         ];
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index 8adba9572049..a0b76c82ece1 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -74,6 +74,10 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
     });
     const [targetTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`, {canBeMissing: true});
     const [currentUserEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector, canBeMissing: false});
+    const sourceTransactionThreadReportID = getTransactionThreadReportID(sourceTransaction);
+    const [sourceTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${sourceTransactionThreadReportID}`, {canBeMissing: true});
+    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionThreadReport?.policyID}`, {canBeMissing: true});
+    const [targetTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionThreadReport?.policyID}`, {canBeMissing: true});
 
     const [hasErrors, setHasErrors] = useState<Partial<Record<MergeFieldKey, boolean>>>({});
     const [conflictFields, setConflictFields] = useState<MergeFieldKey[]>([]);
@@ -181,8 +185,8 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
 
     // Build merge fields array with all necessary information
     const mergeFields = useMemo(
-        () => buildMergeFieldsData(conflictFields, targetTransaction, sourceTransaction, mergeTransaction, translate),
-        [conflictFields, targetTransaction, sourceTransaction, mergeTransaction, translate],
+        () => buildMergeFieldsData(conflictFields, targetTransaction, sourceTransaction, mergeTransaction, targetTransactionPolicy, sourceTransactionPolicy, translate),
+        [conflictFields, targetTransaction, sourceTransaction, mergeTransaction, targetTransactionPolicy, sourceTransactionPolicy, translate],
     );
 
     // If this screen has multiple "selection cards" on it and the user skips one or more, show an error above the footer button
diff --git a/src/types/onyx/MergeTransaction.ts b/src/types/onyx/MergeTransaction.ts
index 988ff987ec9a..3481d783c5ed 100644
--- a/src/types/onyx/MergeTransaction.ts
+++ b/src/types/onyx/MergeTransaction.ts
@@ -50,6 +50,9 @@ type MergeTransaction = {
 
     /** The report ID of the transaction */
     reportID: string;
+
+    /** Tax percentage value of the transaction */
+    taxValue: string;
 };
 
 export default MergeTransaction;
diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 72dc2c095323..a1e0ef52b940 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -520,7 +520,7 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display value for merchant
-            const result = getDisplayValue('merchant', transaction, translateLocal);
+            const result = getDisplayValue('merchant', transaction, undefined, translateLocal);
 
             // Then it should return empty string
             expect(result).toBe('');
@@ -535,8 +535,8 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display values for boolean fields
-            const reimbursableResult = getDisplayValue('reimbursable', transaction, translateLocal);
-            const billableResult = getDisplayValue('billable', transaction, translateLocal);
+            const reimbursableResult = getDisplayValue('reimbursable', transaction, undefined, translateLocal);
+            const billableResult = getDisplayValue('billable', transaction, undefined, translateLocal);
 
             // Then it should return translated Yes/No values
             expect(reimbursableResult).toBe('common.yes');
@@ -552,7 +552,7 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display value for amount
-            const result = getDisplayValue('amount', transaction, translateLocal);
+            const result = getDisplayValue('amount', transaction, undefined, translateLocal);
 
             // Then it should return formatted currency string
             expect(result).toBe('$10.00');
@@ -568,7 +568,7 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display value for description
-            const result = getDisplayValue('description', transaction, translateLocal);
+            const result = getDisplayValue('description', transaction, undefined, translateLocal);
 
             // Then it should return cleaned text without HTML and with spaces instead of line breaks
             expect(result).toBe('This is a test description with line breaks and more text');
@@ -582,7 +582,7 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display value for tag
-            const result = getDisplayValue('tag', transaction, translateLocal);
+            const result = getDisplayValue('tag', transaction, undefined, translateLocal);
 
             // Then it should return sanitized tag names separated by commas
             expect(result).toBe('Department, Engineering, Frontend');
@@ -598,8 +598,8 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display values for string fields
-            const merchantResult = getDisplayValue('merchant', transaction, translateLocal);
-            const categoryResult = getDisplayValue('category', transaction, translateLocal);
+            const merchantResult = getDisplayValue('merchant', transaction, undefined, translateLocal);
+            const categoryResult = getDisplayValue('category', transaction, undefined, translateLocal);
 
             // Then it should return the string values
             expect(merchantResult).toBe('Starbucks Coffee');

From bd346d2da1ef4eca3b885fd880139f8540ae213a Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 30 Sep 2025 01:21:36 +0700
Subject: [PATCH 02/54] auto calculate tax amount when selecting tax rate or
 amount

---
 .../ReportActionItem/MoneyRequestView.tsx          |  7 ++++---
 src/libs/MergeTransactionUtils.ts                  | 14 ++++++++++++--
 src/pages/TransactionMerge/DetailsReviewPage.tsx   | 13 +++++++++++++
 src/types/onyx/MergeTransaction.ts                 |  6 ++++++
 4 files changed, 35 insertions(+), 5 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 3b1ad6aae3c1..0f3948ce0774 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -210,9 +210,10 @@ function MoneyRequestView({
     const isInvoice = isInvoiceReport(moneyRequestReport);
     const isTrackExpense = isTrackExpenseReport(report);
     const taxRates = policy?.taxRates;
-    const formattedTaxAmount = updatedTransaction?.taxAmount
-        ? convertToDisplayString(Math.abs(updatedTransaction?.taxAmount), transactionCurrency)
-        : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), transactionCurrency);
+    const formattedTaxAmount =
+        updatedTransaction?.taxAmount !== undefined
+            ? convertToDisplayString(Math.abs(updatedTransaction?.taxAmount), transactionCurrency)
+            : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), transactionCurrency);
 
     const taxRatesDescription = taxRates?.name;
     const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction);
diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 53c80495ec39..92cf680ddca2 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -5,7 +5,7 @@ import CONST from '@src/CONST';
 import type {TranslationPaths} from '@src/languages/types';
 import type {MergeTransaction, Policy, Transaction} from '@src/types/onyx';
 import type {Receipt} from '@src/types/onyx/Transaction';
-import {convertToDisplayString} from './CurrencyUtils';
+import {convertToBackendAmount, convertToDisplayString} from './CurrencyUtils';
 import getReceiptFilenameFromTransaction from './getReceiptFilenameFromTransaction';
 import Parser from './Parser';
 import {getCommaSeparatedTagNameWithSanitizedColons} from './PolicyUtils';
@@ -13,7 +13,7 @@ import {getIOUActionForReportID} from './ReportActionsUtils';
 import {findSelfDMReportID, getReportName, getReportOrDraftReport, getTransactionDetails} from './ReportUtils';
 import type {TransactionDetails} from './ReportUtils';
 import StringUtils from './StringUtils';
-import {getCurrency, getReimbursable, getTaxName, isCardTransaction, isMerchantMissing} from './TransactionUtils';
+import {calculateTaxAmount, getCurrency, getReimbursable, getTaxName, isCardTransaction, isMerchantMissing} from './TransactionUtils';
 
 const RECEIPT_SOURCE_URL = 'https://www.expensify.com/receipts/';
 
@@ -161,6 +161,12 @@ function getMergeFieldTranslationKey(field: MergeFieldKey) {
     return MERGE_FIELD_TRANSLATION_KEYS[field];
 }
 
+function getMergeTaxAmount(taxPercentage: string | undefined, amount: number, currency: string) {
+    const taxAmount = calculateTaxAmount(taxPercentage, amount, currency);
+    const taxAmountInSmallestCurrencyUnits = convertToBackendAmount(Number.parseFloat(taxAmount.toString()));
+    return taxAmountInSmallestCurrencyUnits;
+}
+
 /**
  * Get mergeableData data if one is missing, and conflict fields that need to be resolved by the user
  * @param targetTransaction - The target transaction
@@ -301,6 +307,9 @@ function buildMergedTransactionData(targetTransaction: OnyxEntry<Transaction>, m
         created: mergeTransaction.created,
         modifiedCreated: mergeTransaction.created,
         reportID: mergeTransaction.reportID,
+        taxValue: mergeTransaction.taxValue,
+        taxAmount: mergeTransaction.taxAmount,
+        taxCode: mergeTransaction.taxCode,
     };
 }
 
@@ -422,6 +431,7 @@ export {
     getDisplayValue,
     buildMergeFieldsData,
     getReportIDForExpense,
+    getMergeTaxAmount,
 };
 
 export type {MergeFieldKey, MergeFieldData};
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index 54c21c92fff8..e7f468c03f27 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -19,6 +19,7 @@ import {
     buildMergeFieldsData,
     getMergeableDataAndConflictFields,
     getMergeFieldValue,
+    getMergeTaxAmount,
     getSourceTransactionFromMergeTransaction,
     getTargetTransactionFromMergeTransaction,
     getTransactionThreadReportID,
@@ -154,6 +155,18 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
 
             // Update both the field value and track which transaction was selected (persisted in Onyx)
             const currentSelections = mergeTransaction?.selectedTransactionByField ?? {};
+
+            // Update tax amount when either tax value or amount is selected
+            if (mergeTransaction && ((field === 'taxValue' && !isEmptyMergeValue(mergeTransaction.amount)) || (field === 'amount' && !isEmptyMergeValue(mergeTransaction.taxValue)))) {
+                setMergeTransactionKey(transactionID, {
+                    taxAmount:
+                        field === 'taxValue'
+                            ? getMergeTaxAmount(transaction.taxValue, mergeTransaction.amount, mergeTransaction.currency)
+                            : getMergeTaxAmount(mergeTransaction.taxValue, transaction.amount, transaction.currency),
+                    taxCode: field === 'taxValue' ? transaction.taxCode : mergeTransaction.taxCode,
+                });
+            }
+
             setMergeTransactionKey(transactionID, {
                 [field]: fieldValue,
                 ...(field === 'amount' && {currency: getCurrency(transaction)}),
diff --git a/src/types/onyx/MergeTransaction.ts b/src/types/onyx/MergeTransaction.ts
index 3481d783c5ed..6c1d48e3e4e6 100644
--- a/src/types/onyx/MergeTransaction.ts
+++ b/src/types/onyx/MergeTransaction.ts
@@ -53,6 +53,12 @@ type MergeTransaction = {
 
     /** Tax percentage value of the transaction */
     taxValue: string;
+
+    /** Tax amount of the transaction */
+    taxAmount: number;
+
+    /** Tax code of the transaction */
+    taxCode: string;
 };
 
 export default MergeTransaction;

From cee6050591cfa5296e4e0ef0cc5cd4aa2e0340e4 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 30 Sep 2025 01:46:41 +0700
Subject: [PATCH 03/54] auto calculate tax amount when selecting tax value

---
 src/libs/MergeTransactionUtils.ts                |  5 ++++-
 src/pages/TransactionMerge/DetailsReviewPage.tsx | 16 ++--------------
 2 files changed, 6 insertions(+), 15 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 92cf680ddca2..a263bdac476e 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -241,6 +241,9 @@ function getMergeableDataAndConflictFields(targetTransaction: OnyxEntry<Transact
 
         if (isTargetValueEmpty || isSourceValueEmpty || targetValue === sourceValue) {
             mergeableData[field] = isTargetValueEmpty ? sourceValue : targetValue;
+            if (field === 'taxValue') {
+                mergeableData.taxCode = isTargetValueEmpty ? sourceTransaction?.taxCode : targetTransaction?.taxCode;
+            }
         } else {
             conflictFields.push(field);
         }
@@ -308,7 +311,7 @@ function buildMergedTransactionData(targetTransaction: OnyxEntry<Transaction>, m
         modifiedCreated: mergeTransaction.created,
         reportID: mergeTransaction.reportID,
         taxValue: mergeTransaction.taxValue,
-        taxAmount: mergeTransaction.taxAmount,
+        taxAmount: getMergeTaxAmount(mergeTransaction.taxValue, mergeTransaction.amount, mergeTransaction.currency),
         taxCode: mergeTransaction.taxCode,
     };
 }
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index e7f468c03f27..75c52c4095b9 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -19,7 +19,6 @@ import {
     buildMergeFieldsData,
     getMergeableDataAndConflictFields,
     getMergeFieldValue,
-    getMergeTaxAmount,
     getSourceTransactionFromMergeTransaction,
     getTargetTransactionFromMergeTransaction,
     getTransactionThreadReportID,
@@ -31,7 +30,7 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig
 import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
 import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
 import {getTransactionDetails} from '@libs/ReportUtils';
-import {getCurrency} from '@libs/TransactionUtils';
+import {getCurrency, getTaxCode} from '@libs/TransactionUtils';
 import {createTransactionThreadReport, openReport} from '@userActions/Report';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -155,21 +154,10 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
 
             // Update both the field value and track which transaction was selected (persisted in Onyx)
             const currentSelections = mergeTransaction?.selectedTransactionByField ?? {};
-
-            // Update tax amount when either tax value or amount is selected
-            if (mergeTransaction && ((field === 'taxValue' && !isEmptyMergeValue(mergeTransaction.amount)) || (field === 'amount' && !isEmptyMergeValue(mergeTransaction.taxValue)))) {
-                setMergeTransactionKey(transactionID, {
-                    taxAmount:
-                        field === 'taxValue'
-                            ? getMergeTaxAmount(transaction.taxValue, mergeTransaction.amount, mergeTransaction.currency)
-                            : getMergeTaxAmount(mergeTransaction.taxValue, transaction.amount, transaction.currency),
-                    taxCode: field === 'taxValue' ? transaction.taxCode : mergeTransaction.taxCode,
-                });
-            }
-
             setMergeTransactionKey(transactionID, {
                 [field]: fieldValue,
                 ...(field === 'amount' && {currency: getCurrency(transaction)}),
+                ...(field === 'taxValue' && {taxCode: getTaxCode(transaction)}),
                 selectedTransactionByField: {
                     ...currentSelections,
                     [field]: transaction.transactionID,

From 20274b36f1cd743ef5c57b9de1ff47c6d0bc1548 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 30 Sep 2025 02:18:49 +0700
Subject: [PATCH 04/54] centralize merge tax logic

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 4 ++--
 src/libs/API/parameters/MergeTransactionParams.ts    | 9 +++++++++
 src/libs/actions/MergeTransaction.ts                 | 3 +++
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 0f3948ce0774..84db652a0f45 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -212,8 +212,8 @@ function MoneyRequestView({
     const taxRates = policy?.taxRates;
     const formattedTaxAmount =
         updatedTransaction?.taxAmount !== undefined
-            ? convertToDisplayString(Math.abs(updatedTransaction?.taxAmount), transactionCurrency)
-            : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), transactionCurrency);
+            ? convertToDisplayString(Math.abs(updatedTransaction.taxAmount), actualCurrency)
+            : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), actualCurrency);
 
     const taxRatesDescription = taxRates?.name;
     const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction);
diff --git a/src/libs/API/parameters/MergeTransactionParams.ts b/src/libs/API/parameters/MergeTransactionParams.ts
index 9fb0c04ebdb1..140e9878b561 100644
--- a/src/libs/API/parameters/MergeTransactionParams.ts
+++ b/src/libs/API/parameters/MergeTransactionParams.ts
@@ -28,6 +28,15 @@ type MergeTransactionParams = {
 
     /** The receiptID we want to keep */
     receiptID: number | undefined;
+
+    /** Tax percentage value we're keeping */
+    taxValue: string;
+
+    /** Tax amount we're keeping */
+    taxAmount: number;
+
+    /** Tax code we're keeping */
+    taxCode: string;
 };
 
 export default MergeTransactionParams;
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 807d10e29d55..c5a4d18e2768 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -181,6 +181,9 @@ function mergeTransactionRequest(mergeTransactionID: string, mergeTransaction: M
         tag: mergeTransaction.tag,
         receiptID: mergeTransaction.receipt?.receiptID,
         reportID: mergeTransaction.reportID,
+        taxValue: mergeTransaction.taxValue,
+        taxAmount: mergeTransaction.taxAmount,
+        taxCode: mergeTransaction.taxCode,
     };
 
     const targetTransactionUpdated = getOptimisticTargetTransactionData(targetTransaction, mergeTransaction);

From 33185a351e6b5b5d3c54185aad04ab643be3d31e Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 30 Sep 2025 02:20:38 +0700
Subject: [PATCH 05/54] fix typescript check

---
 tests/utils/collections/mergeTransaction.ts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/utils/collections/mergeTransaction.ts b/tests/utils/collections/mergeTransaction.ts
index dd45e28fa951..0ce796037df0 100644
--- a/tests/utils/collections/mergeTransaction.ts
+++ b/tests/utils/collections/mergeTransaction.ts
@@ -23,5 +23,8 @@ export default function createRandomMergeTransaction(index: number): MergeTransa
         receipt: {},
         created: format(randPastDate(), CONST.DATE.FNS_DB_FORMAT_STRING),
         reportID: index.toString(),
+        taxAmount: randAmount(),
+        taxValue: randAmount().toString(),
+        taxCode: randWord(),
     };
 }

From 93172bf684a4a913f1a257c6e89d5552e9e678e3 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 30 Sep 2025 02:45:42 +0700
Subject: [PATCH 06/54] fix unit test

---
 tests/unit/MergeTransactionUtilsTest.ts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index a1e0ef52b940..5e6689f706bc 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -5,6 +5,7 @@ import {
     getMergeableDataAndConflictFields,
     getMergeFieldTranslationKey,
     getMergeFieldValue,
+    getMergeTaxAmount,
     getSourceTransactionFromMergeTransaction,
     isEmptyMergeValue,
     selectTargetAndSourceTransactionsForMerge,
@@ -410,6 +411,8 @@ describe('MergeTransactionUtils', () => {
                 receipt: {receiptID: 1235, source: 'merged.jpg', filename: 'merged.jpg'},
                 created: '2025-01-02T00:00:00.000Z',
                 reportID: '1',
+                taxValue: '9%',
+                taxCode: 'id_TAX_RATE_1',
             };
 
             const result = buildMergedTransactionData(targetTransaction, mergeTransaction);
@@ -435,6 +438,9 @@ describe('MergeTransactionUtils', () => {
                 created: '2025-01-02T00:00:00.000Z',
                 modifiedCreated: '2025-01-02T00:00:00.000Z',
                 reportID: '1',
+                taxValue: '9%',
+                taxAmount: getMergeTaxAmount('9%', 2000, 'USD'),
+                taxCode: 'id_TAX_RATE_1',
             });
         });
     });

From 7b3c39386a84fc8017e105de6c6f872546ce9192 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 2 Oct 2025 01:16:44 +0700
Subject: [PATCH 07/54] remove redundant conversion

---
 src/libs/MergeTransactionUtils.ts       | 9 +--------
 tests/unit/MergeTransactionUtilsTest.ts | 5 +++--
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index a263bdac476e..5ee359c38b74 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -161,12 +161,6 @@ function getMergeFieldTranslationKey(field: MergeFieldKey) {
     return MERGE_FIELD_TRANSLATION_KEYS[field];
 }
 
-function getMergeTaxAmount(taxPercentage: string | undefined, amount: number, currency: string) {
-    const taxAmount = calculateTaxAmount(taxPercentage, amount, currency);
-    const taxAmountInSmallestCurrencyUnits = convertToBackendAmount(Number.parseFloat(taxAmount.toString()));
-    return taxAmountInSmallestCurrencyUnits;
-}
-
 /**
  * Get mergeableData data if one is missing, and conflict fields that need to be resolved by the user
  * @param targetTransaction - The target transaction
@@ -311,7 +305,7 @@ function buildMergedTransactionData(targetTransaction: OnyxEntry<Transaction>, m
         modifiedCreated: mergeTransaction.created,
         reportID: mergeTransaction.reportID,
         taxValue: mergeTransaction.taxValue,
-        taxAmount: getMergeTaxAmount(mergeTransaction.taxValue, mergeTransaction.amount, mergeTransaction.currency),
+        taxAmount: convertToBackendAmount(calculateTaxAmount(mergeTransaction.taxValue, mergeTransaction.amount, mergeTransaction.currency)),
         taxCode: mergeTransaction.taxCode,
     };
 }
@@ -434,7 +428,6 @@ export {
     getDisplayValue,
     buildMergeFieldsData,
     getReportIDForExpense,
-    getMergeTaxAmount,
 };
 
 export type {MergeFieldKey, MergeFieldData};
diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 5e6689f706bc..87d93deaf1ab 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -1,3 +1,4 @@
+import {convertToBackendAmount} from '@libs/CurrencyUtils';
 import {translateLocal} from '@libs/Localize';
 import {
     buildMergedTransactionData,
@@ -5,13 +6,13 @@ import {
     getMergeableDataAndConflictFields,
     getMergeFieldTranslationKey,
     getMergeFieldValue,
-    getMergeTaxAmount,
     getSourceTransactionFromMergeTransaction,
     isEmptyMergeValue,
     selectTargetAndSourceTransactionsForMerge,
     shouldNavigateToReceiptReview,
 } from '@libs/MergeTransactionUtils';
 import {getTransactionDetails} from '@libs/ReportUtils';
+import {calculateTaxAmount} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
 import createRandomMergeTransaction from '../utils/collections/mergeTransaction';
 import createRandomTransaction from '../utils/collections/transaction';
@@ -439,7 +440,7 @@ describe('MergeTransactionUtils', () => {
                 modifiedCreated: '2025-01-02T00:00:00.000Z',
                 reportID: '1',
                 taxValue: '9%',
-                taxAmount: getMergeTaxAmount('9%', 2000, 'USD'),
+                taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, 'USD')),
                 taxCode: 'id_TAX_RATE_1',
             });
         });

From 2175bced0d6fdd286007a3db479f588ecb2da63d Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 9 Oct 2025 16:41:53 +0700
Subject: [PATCH 08/54] only show policy specific fields for supported reports

---
 src/pages/TransactionMerge/ConfirmationPage.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index b9b8e331dcd9..495b7665c2ac 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -45,10 +45,11 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
 
     const targetTransactionThreadReportID = getTransactionThreadReportID(targetTransaction);
     const targetTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`];
-    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionThreadReport?.policyID}`, {canBeMissing: true});
 
     // Build the merged transaction data for display
     const mergedTransactionData = useMemo(() => buildMergedTransactionData(targetTransaction, mergeTransaction), [targetTransaction, mergeTransaction]);
+    const mergedTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${mergedTransactionData?.reportID}`];
+    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${mergedTransactionThreadReport?.policyID ?? targetTransactionThreadReport?.policyID}`, {canBeMissing: true});
 
     const contextValue = useMemo(
         () => ({

From 029421346cc8b4c53b65cabb5073206e980fe4cc Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 9 Oct 2025 18:03:47 +0700
Subject: [PATCH 09/54] update merge transaction in Onyx after calculating tax

---
 src/libs/actions/MergeTransaction.ts            | 2 +-
 src/pages/TransactionMerge/ConfirmationPage.tsx | 8 ++++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 17dd32bf20c8..b91db0ece53f 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -22,7 +22,7 @@ function setupMergeTransactionData(transactionID: string, values: Partial<MergeT
 /**
  * Sets merge transaction data for a specific transaction
  */
-function setMergeTransactionKey(transactionID: string, values: Partial<MergeTransaction>) {
+function setMergeTransactionKey(transactionID: string, values: Partial<MergeTransaction> | null) {
     Onyx.merge(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${transactionID}`, values);
 }
 
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 495b7665c2ac..a090944d7a92 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useMemo, useState} from 'react';
+import React, {useCallback, useEffect, useMemo, useState} from 'react';
 import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -14,7 +14,7 @@ import Text from '@components/Text';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import useThemeStyles from '@hooks/useThemeStyles';
-import {mergeTransactionRequest} from '@libs/actions/MergeTransaction';
+import {mergeTransactionRequest, setMergeTransactionKey} from '@libs/actions/MergeTransaction';
 import {buildMergedTransactionData, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction, getTransactionThreadReportID} from '@libs/MergeTransactionUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -51,6 +51,10 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
     const mergedTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${mergedTransactionData?.reportID}`];
     const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${mergedTransactionThreadReport?.policyID ?? targetTransactionThreadReport?.policyID}`, {canBeMissing: true});
 
+    useEffect(() => {
+        setMergeTransactionKey(transactionID, mergedTransactionData);
+    }, [mergedTransactionData, transactionID]);
+
     const contextValue = useMemo(
         () => ({
             transactionThreadReport: targetTransactionThreadReport,

From d0c7366daea3781c2d0fa43a2256c4961652c8b0 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 14 Oct 2025 16:58:34 +0700
Subject: [PATCH 10/54] fix typecheck

---
 tests/unit/MergeTransactionUtilsTest.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 1e1e97b00758..63680bb25885 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -701,7 +701,7 @@ describe('MergeTransactionUtils', () => {
                 {email: 'test1@example.com', displayName: 'Test User 1', avatarUrl: '', login: 'test1'},
             ];
 
-            const result = getDisplayValue('attendees', transaction, translateLocal);
+            const result = getDisplayValue('attendees', transaction, undefined, translateLocal);
 
             expect(result).toBe('Test User 2, Test User 1');
         });

From 84cbc1b8d849c2240be5d4624b41b3ab1a4a8708 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 17 Oct 2025 02:32:53 +0700
Subject: [PATCH 11/54] revert: only show policy specific fields for supported
 reports

---
 src/pages/TransactionMerge/ConfirmationPage.tsx | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index e0dbf26c62ff..9ee9befcb56c 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -52,8 +52,6 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
 
     // Build the merged transaction data for display
     const mergedTransactionData = useMemo(() => buildMergedTransactionData(targetTransaction, mergeTransaction), [targetTransaction, mergeTransaction]);
-    const mergedTransactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${mergedTransactionData?.reportID}`];
-    const [mergedTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${mergedTransactionThreadReport?.policyID ?? targetTransactionThreadReport?.policyID}`, {canBeMissing: true});
 
     useEffect(() => {
         setMergeTransactionKey(transactionID, mergedTransactionData);
@@ -114,7 +112,7 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
                     <ShowContextMenuContext.Provider value={contextValue}>
                         <MoneyRequestView
                             allReports={allReports}
-                            expensePolicy={mergedTransactionPolicy}
+                            expensePolicy={policy}
                             report={targetTransactionThreadReport}
                             shouldShowAnimatedBackground={false}
                             readonly

From b7d9c6b015b8df4732522c35cc8b6816299c3099 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 17 Oct 2025 03:23:06 +0700
Subject: [PATCH 12/54] sort eligible transactions list by creation data

---
 .../MergeTransactionsListContent.tsx               | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
index 605826866606..0deb0331067b 100644
--- a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
+++ b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
@@ -65,12 +65,14 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr
     const sections = useMemo(() => {
         return [
             {
-                data: (eligibleTransactions ?? []).map((eligibleTransaction) => ({
-                    ...fillMissingReceiptSource(eligibleTransaction),
-                    keyForList: eligibleTransaction.transactionID,
-                    isSelected: eligibleTransaction.transactionID === mergeTransaction?.sourceTransactionID,
-                    errors: eligibleTransaction.errors as Errors | undefined,
-                })),
+                data: (eligibleTransactions ?? [])
+                    .map((eligibleTransaction) => ({
+                        ...fillMissingReceiptSource(eligibleTransaction),
+                        keyForList: eligibleTransaction.transactionID,
+                        isSelected: eligibleTransaction.transactionID === mergeTransaction?.sourceTransactionID,
+                        errors: eligibleTransaction.errors as Errors | undefined,
+                    }))
+                    .sort((a, b) => b.created.localeCompare(a.created)),
                 shouldShow: true,
             },
         ];

From 3e06a582ca6d9553d2d34f62a8d723cd1fff1fb5 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 17 Oct 2025 03:23:20 +0700
Subject: [PATCH 13/54] save tax optimistically

---
 src/libs/actions/MergeTransaction.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 61f328934a16..98dbd5da40a1 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -159,7 +159,7 @@ function getOnyxTargetTransactionData(
     const targetTransactionDetails = getTransactionDetails(targetTransaction);
     const filteredTransactionChanges = Object.fromEntries(
         Object.entries(mergeTransaction).filter(([key, mergeValue]) => {
-            if (!(MERGE_FIELDS as readonly string[]).includes(key)) {
+            if (key !== 'taxCode' && key !== 'taxAmount' && !(MERGE_FIELDS as readonly string[]).includes(key)) {
                 return false;
             }
 

From 552fafef45a43a3bf0bce8f5a4a79df061d816f2 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 17 Oct 2025 03:54:44 +0700
Subject: [PATCH 14/54] fix lint

---
 src/pages/TransactionMerge/MergeTransactionsListContent.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
index 0deb0331067b..f62fba4f5c37 100644
--- a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
+++ b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
@@ -72,11 +72,11 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr
                         isSelected: eligibleTransaction.transactionID === mergeTransaction?.sourceTransactionID,
                         errors: eligibleTransaction.errors as Errors | undefined,
                     }))
-                    .sort((a, b) => b.created.localeCompare(a.created)),
+                    .sort((a, b) => localeCompare(b.created, a.created)),
                 shouldShow: true,
             },
         ];
-    }, [eligibleTransactions, mergeTransaction]);
+    }, [eligibleTransactions, mergeTransaction, localeCompare]);
 
     const handleSelectRow = useCallback(
         (item: MergeTransactionListItemType) => {

From d4011dea264c631230813e2fcc2730699b5b5fb2 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 20 Oct 2025 17:24:47 +0700
Subject: [PATCH 15/54] refactor: define list of derived merge fields

---
 src/libs/MergeTransactionUtils.ts    | 3 +++
 src/libs/actions/MergeTransaction.ts | 4 ++--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 1a1820d0e63a..1a1ccf084ea2 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -21,6 +21,8 @@ const RECEIPT_SOURCE_URL = 'https://www.expensify.com/receipts/';
 
 // Define the specific merge fields we want to handle
 const MERGE_FIELDS = ['amount', 'currency', 'merchant', 'created', 'category', 'tag', 'description', 'taxValue', 'reimbursable', 'billable', 'attendees', 'reportID'] as const;
+// Some fields are dependant on others. We need to automatically derive the correct field values depending on user selection.
+const DERIVED_MERGE_FIELDS = [...MERGE_FIELDS, 'taxCode', 'taxAmount'] as const;
 type MergeFieldKey = TupleToUnion<typeof MERGE_FIELDS>;
 type MergeFieldOption = {
     transaction: Transaction;
@@ -459,6 +461,7 @@ export {
     getReportIDForExpense,
     getMergeFieldErrorText,
     MERGE_FIELDS,
+    DERIVED_MERGE_FIELDS,
 };
 
 export type {MergeFieldKey, MergeFieldData};
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 98dbd5da40a1..f6c8af5ccc55 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -4,7 +4,7 @@ import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
 import * as API from '@libs/API';
 import type {GetTransactionsForMergingParams} from '@libs/API/parameters';
 import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
-import {getMergeFieldValue, getTransactionThreadReportID, MERGE_FIELDS} from '@libs/MergeTransactionUtils';
+import {DERIVED_MERGE_FIELDS, getMergeFieldValue, getTransactionThreadReportID} from '@libs/MergeTransactionUtils';
 import type {MergeFieldKey} from '@libs/MergeTransactionUtils';
 import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
 import {getIOUActionForReportID} from '@libs/ReportActionsUtils';
@@ -159,7 +159,7 @@ function getOnyxTargetTransactionData(
     const targetTransactionDetails = getTransactionDetails(targetTransaction);
     const filteredTransactionChanges = Object.fromEntries(
         Object.entries(mergeTransaction).filter(([key, mergeValue]) => {
-            if (key !== 'taxCode' && key !== 'taxAmount' && !(MERGE_FIELDS as readonly string[]).includes(key)) {
+            if (!(DERIVED_MERGE_FIELDS as readonly string[]).includes(key)) {
                 return false;
             }
 

From 899649a5dec21d8faecf7079e02646496a27f2fc Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 23 Oct 2025 23:34:25 +0700
Subject: [PATCH 16/54] use comment.udfs

---
 src/libs/API/parameters/MergeTransactionParams.ts | 9 ---------
 src/libs/actions/MergeTransaction.ts              | 7 ++++---
 2 files changed, 4 insertions(+), 12 deletions(-)

diff --git a/src/libs/API/parameters/MergeTransactionParams.ts b/src/libs/API/parameters/MergeTransactionParams.ts
index 140e9878b561..9fb0c04ebdb1 100644
--- a/src/libs/API/parameters/MergeTransactionParams.ts
+++ b/src/libs/API/parameters/MergeTransactionParams.ts
@@ -28,15 +28,6 @@ type MergeTransactionParams = {
 
     /** The receiptID we want to keep */
     receiptID: number | undefined;
-
-    /** Tax percentage value we're keeping */
-    taxValue: string;
-
-    /** Tax amount we're keeping */
-    taxAmount: number;
-
-    /** Tax code we're keeping */
-    taxCode: string;
 };
 
 export default MergeTransactionParams;
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 8e5773db88d5..50ebf62a3e4f 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -224,15 +224,16 @@ function mergeTransactionRequest({mergeTransactionID, mergeTransaction, targetTr
             ...targetTransaction.comment,
             comment: mergeTransaction.description,
             attendees: mergeTransaction.attendees,
+            udfs: {
+                modifiedTaxAmount: mergeTransaction.taxAmount,
+                value: mergeTransaction.taxValue,
+            },
         }),
         billable: mergeTransaction.billable,
         reimbursable: mergeTransaction.reimbursable,
         tag: mergeTransaction.tag,
         receiptID: mergeTransaction.receipt?.receiptID,
         reportID: mergeTransaction.reportID,
-        taxValue: mergeTransaction.taxValue,
-        taxAmount: mergeTransaction.taxAmount,
-        taxCode: mergeTransaction.taxCode,
     };
 
     const onyxTargetTransactionData = getOnyxTargetTransactionData(targetTransaction, mergeTransaction, policy, policyTags, policyCategories);

From 7ba8864e4607566cad8b16a9164b0a0e2c8a2846 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 31 Oct 2025 06:04:40 +0700
Subject: [PATCH 17/54] remove redundant changes

---
 src/libs/MergeTransactionUtils.ts | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 839c8013059d..e143949d3ac7 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -394,10 +394,6 @@ function getDisplayValue(field: MergeFieldKey, transaction: Transaction, policy:
         return getTaxName(policy, transaction) ?? '';
     }
 
-    if (field === 'taxValue') {
-        return getTaxName(policy, transaction) ?? '';
-    }
-
     return SafeString(fieldValue);
 }
 /**

From a29850e247841a610e358f3817bfe33c541efaaf Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 31 Oct 2025 06:31:09 +0700
Subject: [PATCH 18/54] incrementally update tax and amount based on selection

---
 src/libs/MergeTransactionUtils.ts             | 42 ++++++++++++++++++-
 src/libs/actions/MergeTransaction.ts          |  8 ++--
 .../TransactionMerge/ConfirmationPage.tsx     |  8 +---
 .../TransactionMerge/DetailsReviewPage.tsx    |  7 ++--
 4 files changed, 49 insertions(+), 16 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index e143949d3ac7..4fb174d2d520 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -37,6 +37,9 @@ type MergeFieldData = {
     options: MergeFieldOption[];
 };
 
+/** Type for merge transaction values that can be null to clear existing values in Onyx */
+type MergeTransactionUpdateValues = Partial<Record<keyof MergeTransaction, MergeTransaction[keyof MergeTransaction] | null>>;
+
 const MERGE_FIELD_TRANSLATION_KEYS = {
     amount: 'iou.amount',
     currency: 'iou.currency',
@@ -332,7 +335,7 @@ function buildMergedTransactionData(targetTransaction: OnyxEntry<Transaction>, m
         modifiedCreated: mergeTransaction.created,
         reportID: mergeTransaction.reportID,
         taxValue: mergeTransaction.taxValue,
-        taxAmount: convertToBackendAmount(calculateTaxAmount(mergeTransaction.taxValue, mergeTransaction.amount, mergeTransaction.currency)),
+        taxAmount: mergeTransaction.taxAmount,
         taxCode: mergeTransaction.taxCode,
     };
 }
@@ -444,6 +447,40 @@ function buildMergeFieldsData(
     });
 }
 
+type GetMergeFieldUpdatedValuesParams<K extends MergeFieldKey> = {
+    transaction: OnyxEntry<Transaction>;
+    field: K;
+    fieldValue: MergeTransaction[K];
+    mergeTransaction: OnyxEntry<MergeTransaction>;
+};
+
+/**
+ * Build updated values for merge transaction field selection
+ * Handles special cases like currency for amount field, reportID
+ */
+function getMergeFieldUpdatedValues<K extends MergeFieldKey>(params: GetMergeFieldUpdatedValuesParams<K>): MergeTransactionUpdateValues {
+    const {transaction, field, fieldValue, mergeTransaction} = params;
+    const updatedValues: MergeTransactionUpdateValues = {
+        [field]: fieldValue,
+    };
+
+    if (field === 'amount') {
+        updatedValues.currency = getCurrency(transaction);
+        if (mergeTransaction?.taxValue && transaction?.amount) {
+            updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(mergeTransaction?.taxValue, transaction.amount, getCurrency(transaction)));
+        }
+    }
+
+    if (field === 'taxValue') {
+        updatedValues.taxCode = transaction?.taxCode;
+        if (mergeTransaction?.amount) {
+            updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(transaction?.taxValue, mergeTransaction.amount, mergeTransaction.currency));
+        }
+    }
+
+    return updatedValues;
+}
+
 export {
     getSourceTransactionFromMergeTransaction,
     getTargetTransactionFromMergeTransaction,
@@ -460,9 +497,10 @@ export {
     getDisplayValue,
     buildMergeFieldsData,
     getReportIDForExpense,
+    getMergeFieldUpdatedValues,
     getMergeFieldErrorText,
     MERGE_FIELDS,
     DERIVED_MERGE_FIELDS,
 };
 
-export type {MergeFieldKey, MergeFieldData};
+export type {MergeFieldKey, MergeFieldData, MergeTransactionUpdateValues};
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 50ebf62a3e4f..3850dca2c7b3 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -1,11 +1,11 @@
 import {deepEqual} from 'fast-equals';
 import Onyx from 'react-native-onyx';
-import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry, OnyxMergeInput, OnyxUpdate} from 'react-native-onyx';
 import * as API from '@libs/API';
 import type {GetTransactionsForMergingParams} from '@libs/API/parameters';
 import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
 import {DERIVED_MERGE_FIELDS, getMergeFieldValue, getTransactionThreadReportID} from '@libs/MergeTransactionUtils';
-import type {MergeFieldKey} from '@libs/MergeTransactionUtils';
+import type {MergeFieldKey, MergeTransactionUpdateValues} from '@libs/MergeTransactionUtils';
 import {isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
 import {getIOUActionForReportID} from '@libs/ReportActionsUtils';
 import {
@@ -34,8 +34,8 @@ function setupMergeTransactionData(transactionID: string, values: Partial<MergeT
 /**
  * Sets merge transaction data for a specific transaction
  */
-function setMergeTransactionKey(transactionID: string, values: Partial<MergeTransaction> | null) {
-    Onyx.merge(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${transactionID}`, values);
+function setMergeTransactionKey(transactionID: string, values: MergeTransactionUpdateValues) {
+    Onyx.merge(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${transactionID}`, values as OnyxMergeInput<`${typeof ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${string}`>);
 }
 
 /**
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 3690deb728f1..0f6b2dee426d 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useMemo, useState} from 'react';
+import React, {useCallback, useMemo, useState} from 'react';
 import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -14,7 +14,7 @@ import Text from '@components/Text';
 import useLocalize from '@hooks/useLocalize';
 import useOnyx from '@hooks/useOnyx';
 import useThemeStyles from '@hooks/useThemeStyles';
-import {mergeTransactionRequest, setMergeTransactionKey} from '@libs/actions/MergeTransaction';
+import {mergeTransactionRequest} from '@libs/actions/MergeTransaction';
 import {buildMergedTransactionData, getReportIDForExpense, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -65,10 +65,6 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
     // Build the merged transaction data for display
     const mergedTransactionData = useMemo(() => buildMergedTransactionData(targetTransaction, mergeTransaction), [targetTransaction, mergeTransaction]);
 
-    useEffect(() => {
-        setMergeTransactionKey(transactionID, mergedTransactionData);
-    }, [mergedTransactionData, transactionID]);
-
     const contextValue = useMemo(
         () => ({
             transactionThreadReport: targetTransactionThreadReport,
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index 06dc5e039191..a87fa2bab05b 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -19,6 +19,7 @@ import {
     buildMergeFieldsData,
     getMergeableDataAndConflictFields,
     getMergeFieldErrorText,
+    getMergeFieldUpdatedValues,
     getMergeFieldValue,
     getSourceTransactionFromMergeTransaction,
     getTargetTransactionFromMergeTransaction,
@@ -31,7 +32,6 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig
 import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
 import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
 import {getTransactionDetails} from '@libs/ReportUtils';
-import {getCurrency, getTaxCode} from '@libs/TransactionUtils';
 import {createTransactionThreadReport, openReport} from '@userActions/Report';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -155,10 +155,9 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
 
             // Update both the field value and track which transaction was selected (persisted in Onyx)
             const currentSelections = mergeTransaction?.selectedTransactionByField ?? {};
+            const updatedValues = getMergeFieldUpdatedValues({transaction, field, fieldValue, mergeTransaction});
             setMergeTransactionKey(transactionID, {
-                [field]: fieldValue,
-                ...(field === 'amount' && {currency: getCurrency(transaction)}),
-                ...(field === 'taxValue' && {taxCode: getTaxCode(transaction)}),
+                ...updatedValues,
                 selectedTransactionByField: {
                     ...currentSelections,
                     [field]: transaction.transactionID,

From 3ce7d2f3c6398d41370abccf9d80b2191bdebd79 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 31 Oct 2025 06:32:39 +0700
Subject: [PATCH 19/54] send taxCode to Transaction_Merge API

---
 src/libs/actions/MergeTransaction.ts | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 3850dca2c7b3..03367306de64 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -224,14 +224,11 @@ function mergeTransactionRequest({mergeTransactionID, mergeTransaction, targetTr
             ...targetTransaction.comment,
             comment: mergeTransaction.description,
             attendees: mergeTransaction.attendees,
-            udfs: {
-                modifiedTaxAmount: mergeTransaction.taxAmount,
-                value: mergeTransaction.taxValue,
-            },
         }),
         billable: mergeTransaction.billable,
         reimbursable: mergeTransaction.reimbursable,
         tag: mergeTransaction.tag,
+        taxCode: mergeTransaction.taxCode,
         receiptID: mergeTransaction.receipt?.receiptID,
         reportID: mergeTransaction.reportID,
     };

From 130ff06ccb5a231b0f791600e2479d1e31376fcb Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 4 Nov 2025 00:03:20 +0700
Subject: [PATCH 20/54] fix unit test

---
 tests/unit/MergeTransactionUtilsTest.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 3180346177c4..be74284f209d 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -523,6 +523,7 @@ describe('MergeTransactionUtils', () => {
                 reportID: '1',
                 reportName: 'Test Report',
                 taxValue: '9%',
+                taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, 'USD')),
                 taxCode: 'id_TAX_RATE_1',
             };
 

From d14c2b445b7a5f18580696b729c8cb2759af8851 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 6 Nov 2025 14:58:56 +0700
Subject: [PATCH 21/54] fallback tax rate

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 4afbed8a26df..5e8098baa90d 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -235,7 +235,7 @@ function MoneyRequestView({
     const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction);
 
     const actualTransactionDate = isFromMergeTransaction && updatedTransaction ? getFormattedCreated(updatedTransaction) : transactionDate;
-    const fallbackTaxRateTitle = transaction?.taxValue;
+    const fallbackTaxRateTitle = updatedTransaction?.taxValue ?? transaction?.taxValue;
 
     const isSettled = isSettledReportUtils(moneyRequestReport?.reportID);
     const isCancelled = moneyRequestReport && moneyRequestReport?.isCancelledIOU;

From 56387698b77fbd53792df49d06462d1bc7b5efbd Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 10 Nov 2025 17:36:05 +0700
Subject: [PATCH 22/54] auto merge taxAmount

---
 src/libs/MergeTransactionUtils.ts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 53d439b13e99..28a9dac24790 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -269,6 +269,9 @@ function getMergeableDataAndConflictFields(targetTransaction: OnyxEntry<Transact
             mergeableData[field] = isTargetValueEmpty ? sourceValue : targetValue;
             if (field === 'taxValue') {
                 mergeableData.taxCode = isTargetValueEmpty ? sourceTransaction?.taxCode : targetTransaction?.taxCode;
+                if (mergeableData.amount) {
+                    mergeableData.taxAmount = convertToBackendAmount(calculateTaxAmount(mergeableData.taxValue as string, mergeableData.amount as number, mergeableData.currency as string));
+                }
             }
         } else {
             conflictFields.push(field);

From 331bb463b9d153b64791683c943fcfc6ff8cb776 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 24 Nov 2025 19:33:44 +0700
Subject: [PATCH 23/54] fix unit test

---
 tests/unit/MergeTransactionUtilsTest.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 17cd2b844eea..c472867699d0 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -768,7 +768,7 @@ describe('MergeTransactionUtils', () => {
             const fieldValue = 'New Merchant Name';
 
             // When we get updated values for merchant field
-            const result = getMergeFieldUpdatedValues(transaction, 'merchant', fieldValue);
+            const result = getMergeFieldUpdatedValues({transaction, field: 'merchant', fieldValue, mergeTransaction: undefined});
 
             // Then it should return an object with the field value
             expect(result).toEqual({
@@ -785,7 +785,7 @@ describe('MergeTransactionUtils', () => {
             const fieldValue = 2500;
 
             // When we get updated values for amount field
-            const result = getMergeFieldUpdatedValues(transaction, 'amount', fieldValue);
+            const result = getMergeFieldUpdatedValues({transaction, field: 'amount', fieldValue, mergeTransaction: undefined});
 
             // Then it should include both amount and currency
             expect(result).toEqual({
@@ -816,7 +816,7 @@ describe('MergeTransactionUtils', () => {
             const fieldValue = 'New Distance Merchant';
 
             // When we get updated values for merchant field
-            const result = getMergeFieldUpdatedValues(transaction, 'merchant', fieldValue);
+            const result = getMergeFieldUpdatedValues({transaction, field: 'merchant', fieldValue, mergeTransaction: undefined});
 
             // Then it should include merchant plus all distance-specific fields
             expect(result).toEqual({

From 6af2fd0a1bbd1ca8b45a0f6a07af8831f3c0fc93 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 24 Nov 2025 19:34:38 +0700
Subject: [PATCH 24/54] fix source transaction tax name is missing

---
 src/libs/MergeTransactionUtils.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index d69cec592421..922819e3caba 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -421,7 +421,7 @@ function getDisplayValue(field: MergeFieldKey, transaction: Transaction, policy:
     }
 
     if (field === 'taxValue') {
-        return getTaxName(policy, transaction) ?? '';
+        return getTaxName(policy, transaction) ?? transaction.taxValue ?? '';
     }
 
     return SafeString(fieldValue);

From 823cc4c994d5d3c9b568145927192ef49647e9b7 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 27 Nov 2025 15:00:16 +0700
Subject: [PATCH 25/54] fix typecheck

---
 src/libs/MergeTransactionUtils.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 15b072e76bf0..ca4258c80915 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -255,7 +255,7 @@ function getMergeableDataAndConflictFields(targetTransaction: OnyxEntry<Transact
         // We allow user to select unreported report
         if (field === 'reportID') {
             if (targetValue === sourceValue) {
-                const updatedValues = getMergeFieldUpdatedValues(targetTransaction, field, SafeString(targetValue));
+                const updatedValues = getMergeFieldUpdatedValues({transaction: targetTransaction, field, fieldValue: SafeString(targetValue)});
                 Object.assign(mergeableData, updatedValues);
             } else {
                 conflictFields.push(field);

From 9bbc0ffb7a0486215b7f91287fa508717fce8877 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 27 Nov 2025 15:01:58 +0700
Subject: [PATCH 26/54] fix typecheck

---
 tests/unit/MergeTransactionUtilsTest.ts | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 710f19e1c1fc..890f0fe6225c 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -800,7 +800,7 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display value for reportID
-            const result = getDisplayValue('reportID', transaction, translateLocal);
+            const result = getDisplayValue('reportID', transaction, undefined, translateLocal);
 
             // Then it should return translated "None"
             expect(result).toBe('common.none');
@@ -815,7 +815,7 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display value for reportID
-            const result = getDisplayValue('reportID', transaction, translateLocal);
+            const result = getDisplayValue('reportID', transaction, undefined, translateLocal);
 
             // Then it should return the reportName
             expect(result).toBe('Test Report Name');
@@ -841,7 +841,7 @@ describe('MergeTransactionUtils', () => {
             };
 
             // When we get display value for reportID
-            const result = getDisplayValue('reportID', transaction, translateLocal);
+            const result = getDisplayValue('reportID', transaction, undefined, translateLocal);
 
             // Then it should return the report's name from Onyx
             expect(result).toBe(report.reportName);
@@ -891,7 +891,7 @@ describe('MergeTransactionUtils', () => {
             const fieldValue = '456';
 
             // When we get updated values for reportID field
-            const result = getMergeFieldUpdatedValues(transaction, 'reportID', fieldValue);
+            const result = getMergeFieldUpdatedValues({transaction, field: 'reportID', fieldValue});
 
             // Then it should include both reportID and reportName
             expect(result).toEqual({

From cd3db9449cf02449fb0cad2b68f20ddcc96a2dce Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 2 Dec 2025 02:08:52 +0700
Subject: [PATCH 27/54] fix: tax does not show in confirm step if target policy
 does not support tax

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 10 +++++++---
 src/pages/TransactionMerge/ConfirmationPage.tsx      |  8 +++++++-
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 539367cc7b33..03094637499c 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -98,7 +98,7 @@ type MoneyRequestViewProps = {
     /** The report currently being looked at */
     report: OnyxEntry<OnyxTypes.Report>;
 
-    /** Policy that the report belongs to */
+    /** Policy that the report belongs to, or the target transaction policy in merge transaction flow */
     expensePolicy: OnyxEntry<OnyxTypes.Policy>;
 
     /** Whether we should display the animated banner above the component */
@@ -115,12 +115,16 @@ type MoneyRequestViewProps = {
 
     /** Merge transaction ID to show in merge transaction flow */
     mergeTransactionID?: string;
+
+    /** Source transaction policy in merge transaction flow */
+    sourceTransactionPolicy?: OnyxEntry<OnyxTypes.Policy>;
 };
 
 function MoneyRequestView({
     allReports,
     report,
     expensePolicy,
+    sourceTransactionPolicy,
     shouldShowAnimatedBackground,
     readonly = false,
     updatedTransaction,
@@ -244,7 +248,7 @@ function MoneyRequestView({
             : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), actualCurrency);
 
     const taxRatesDescription = taxRates?.name;
-    const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction);
+    const taxRateTitle = updatedTransaction ? getTaxName(sourceTransactionPolicy ?? policy, updatedTransaction) : getTaxName(policy, transaction);
 
     const actualTransactionDate = isFromMergeTransaction && updatedTransaction ? getFormattedCreated(updatedTransaction) : transactionDate;
     const fallbackTaxRateTitle = updatedTransaction?.taxValue ?? transaction?.taxValue;
@@ -308,7 +312,7 @@ function MoneyRequestView({
     const canEditReimbursable = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.REIMBURSABLE, undefined, isChatReportArchived);
     const shouldShowAttendees = useMemo(() => shouldShowAttendeesTransactionUtils(iouType, policy), [iouType, policy]);
 
-    const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat || isExpenseUnreported, policy, isDistanceRequest, isPerDiemRequest);
+    const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat || isExpenseUnreported, sourceTransactionPolicy ?? policy, isDistanceRequest, isPerDiemRequest);
     const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID);
     const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;
 
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 1be16a85a8d1..7ff5c88e7c24 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -17,7 +17,7 @@ import useOnyx from '@hooks/useOnyx';
 import usePermissions from '@hooks/usePermissions';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {mergeTransactionRequest} from '@libs/actions/MergeTransaction';
-import {buildMergedTransactionData, getReportIDForExpense, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils';
+import {buildMergedTransactionData, getReportIDForExpense, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction, getTransactionThreadReportID} from '@libs/MergeTransactionUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
@@ -63,6 +63,11 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
     const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true});
     const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
     const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true});
+
+    const sourceTransactionThreadReportID = getTransactionThreadReportID(sourceTransaction);
+    const [sourceTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${sourceTransactionThreadReportID}`, {canBeMissing: true});
+    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionThreadReport?.policyID}`, {canBeMissing: true});
+
     const currentUserPersonalDetails = useCurrentUserPersonalDetails();
     const currentUserAccountIDParam = currentUserPersonalDetails.accountID;
     const currentUserEmailParam = currentUserPersonalDetails.login ?? '';
@@ -151,6 +156,7 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
                         <MoneyRequestView
                             allReports={allReports}
                             expensePolicy={policy}
+                            sourceTransactionPolicy={sourceTransactionPolicy}
                             report={targetTransactionThreadReport}
                             shouldShowAnimatedBackground={false}
                             readonly

From 25d6e32c7dcfec37df075d76526a2524ddd294a5 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 2 Dec 2025 02:14:49 +0700
Subject: [PATCH 28/54] fix failed checks

---
 src/libs/MergeTransactionUtils.ts                | 3 ++-
 src/pages/TransactionMerge/ConfirmationPage.tsx  | 8 +++++++-
 src/pages/TransactionMerge/DetailsReviewPage.tsx | 2 +-
 3 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index b0706655c5b3..810548248761 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -23,7 +23,8 @@ import {
     getReimbursable,
     getTaxName,
     getWaypoints,
-    isDistanceRequest, isExpenseSplit,
+    isDistanceRequest,
+    isExpenseSplit,
     isManagedCardTransaction,
     isMerchantMissing,
 } from './TransactionUtils';
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 7ff5c88e7c24..13c0b65a86b7 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -17,7 +17,13 @@ import useOnyx from '@hooks/useOnyx';
 import usePermissions from '@hooks/usePermissions';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {mergeTransactionRequest} from '@libs/actions/MergeTransaction';
-import {buildMergedTransactionData, getReportIDForExpense, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction, getTransactionThreadReportID} from '@libs/MergeTransactionUtils';
+import {
+    buildMergedTransactionData,
+    getReportIDForExpense,
+    getSourceTransactionFromMergeTransaction,
+    getTargetTransactionFromMergeTransaction,
+    getTransactionThreadReportID,
+} from '@libs/MergeTransactionUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index 7e05fc02fce7..c8b76c60d0bd 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -170,7 +170,7 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
                 } as Partial<Record<MergeFieldKey, string>>,
             });
         },
-        [mergeTransaction?.selectedTransactionByField, transactionID],
+        [mergeTransaction, transactionID],
     );
 
     // Handle continue

From 2b755941f40cf79d85a169a5b8badbd664d7b318 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 5 Dec 2025 01:27:20 +0700
Subject: [PATCH 29/54] do not use transactionThreadReport

---
 src/pages/TransactionMerge/ConfirmationPage.tsx  | 13 +++----------
 src/pages/TransactionMerge/DetailsReviewPage.tsx |  5 ++---
 2 files changed, 5 insertions(+), 13 deletions(-)

diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 13c0b65a86b7..17a65aba1550 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -17,13 +17,7 @@ import useOnyx from '@hooks/useOnyx';
 import usePermissions from '@hooks/usePermissions';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {mergeTransactionRequest} from '@libs/actions/MergeTransaction';
-import {
-    buildMergedTransactionData,
-    getReportIDForExpense,
-    getSourceTransactionFromMergeTransaction,
-    getTargetTransactionFromMergeTransaction,
-    getTransactionThreadReportID,
-} from '@libs/MergeTransactionUtils';
+import {buildMergedTransactionData, getReportIDForExpense, getSourceTransactionFromMergeTransaction, getTargetTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
@@ -70,9 +64,8 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
     const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
     const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true});
 
-    const sourceTransactionThreadReportID = getTransactionThreadReportID(sourceTransaction);
-    const [sourceTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${sourceTransactionThreadReportID}`, {canBeMissing: true});
-    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionThreadReport?.policyID}`, {canBeMissing: true});
+    const sourceTransactionReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction?.reportID}`];
+    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`, {canBeMissing: true});
 
     const currentUserPersonalDetails = useCurrentUserPersonalDetails();
     const currentUserAccountIDParam = currentUserPersonalDetails.accountID;
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index c8b76c60d0bd..a7254dd4ab29 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -85,10 +85,9 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
     const [originalTargetTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${targetTransaction?.comment?.originalTransactionID}`, {canBeMissing: true});
     const [targetTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`, {canBeMissing: true});
     const [currentUserEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector, canBeMissing: false});
-    const sourceTransactionThreadReportID = getTransactionThreadReportID(sourceTransaction);
-    const [sourceTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${sourceTransactionThreadReportID}`, {canBeMissing: true});
-    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionThreadReport?.policyID}`, {canBeMissing: true});
     const [targetTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionThreadReport?.policyID}`, {canBeMissing: true});
+    const [sourceTransactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction?.reportID}`, {canBeMissing: true});
+    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`, {canBeMissing: true});
     const [hasErrors, setHasErrors] = useState<Partial<Record<MergeFieldKey, boolean>>>({});
     const [conflictFields, setConflictFields] = useState<MergeFieldKey[]>([]);
     const [isCheckingDataBeforeGoNext, setIsCheckingDataBeforeGoNext] = useState<boolean>(false);

From bdf1c6978370d29b26809a6846d6d495d40eb411 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 5 Dec 2025 02:41:12 +0700
Subject: [PATCH 30/54] rely on selectedTransactionByField to know which policy
 does the selected tax comes from

---
 src/components/ReportActionItem/MoneyRequestView.tsx | 12 ++++++------
 src/pages/TransactionMerge/ConfirmationPage.tsx      |  9 ++++++++-
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 7e133cc5c65c..1462ce412f67 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -116,20 +116,20 @@ type MoneyRequestViewProps = {
     /** Merge transaction ID to show in merge transaction flow */
     mergeTransactionID?: string;
 
-    /** Source transaction policy in merge transaction flow */
-    sourceTransactionPolicy?: OnyxEntry<OnyxTypes.Policy>;
+    /** Tax name to display in merge transaction flow */
+    taxName?: string;
 };
 
 function MoneyRequestView({
     allReports,
     report,
     expensePolicy,
-    sourceTransactionPolicy,
     shouldShowAnimatedBackground,
     readonly = false,
     updatedTransaction,
     isFromReviewDuplicates = false,
     mergeTransactionID,
+    taxName,
 }: MoneyRequestViewProps) {
     const icons = useMemoizedLazyExpensifyIcons(['DotIndicator', 'Checkmark', 'Suitcase'] as const);
     const styles = useThemeStyles();
@@ -248,7 +248,7 @@ function MoneyRequestView({
             : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), actualCurrency);
 
     const taxRatesDescription = taxRates?.name;
-    const taxRateTitle = updatedTransaction ? getTaxName(sourceTransactionPolicy ?? policy, updatedTransaction) : getTaxName(policy, transaction);
+    const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction);
 
     const actualTransactionDate = isFromMergeTransaction && updatedTransaction ? getFormattedCreated(updatedTransaction) : transactionDate;
     const fallbackTaxRateTitle = updatedTransaction?.taxValue ?? transaction?.taxValue;
@@ -312,7 +312,7 @@ function MoneyRequestView({
     const canEditReimbursable = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.REIMBURSABLE, undefined, isChatReportArchived);
     const shouldShowAttendees = useMemo(() => shouldShowAttendeesTransactionUtils(iouType, policy), [iouType, policy]);
 
-    const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, sourceTransactionPolicy ?? policy, isDistanceRequest, isPerDiemRequest);
+    const shouldShowTax = !!taxName || isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest, isPerDiemRequest);
     const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID);
     const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;
 
@@ -514,7 +514,7 @@ function MoneyRequestView({
     const decodedCategoryName = getDecodedCategoryName(categoryValue);
     const categoryCopyValue = !canEdit ? decodedCategoryName : undefined;
     const cardCopyValue = cardProgramName;
-    const taxRateValue = taxRateTitle ?? fallbackTaxRateTitle;
+    const taxRateValue = taxName ?? taxRateTitle ?? fallbackTaxRateTitle;
     const taxRateCopyValue = !canEditTaxFields ? taxRateValue : undefined;
     const taxAmountTitle = formattedTaxAmount ? formattedTaxAmount.toString() : '';
     const taxAmountCopyValue = !canEditTaxFields ? taxAmountTitle : undefined;
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 17a65aba1550..4452db03d072 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -22,6 +22,7 @@ import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
 import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
+import {getTaxName} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type SCREENS from '@src/SCREENS';
@@ -66,6 +67,12 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
 
     const sourceTransactionReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction?.reportID}`];
     const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`, {canBeMissing: true});
+    const taxName =
+        mergeTransaction?.taxValue &&
+        getTaxName(
+            mergeTransaction?.selectedTransactionByField?.taxValue === mergeTransaction?.sourceTransactionID ? sourceTransactionPolicy : policy,
+            mergeTransaction as unknown as OnyxEntry<Transaction>,
+        );
 
     const currentUserPersonalDetails = useCurrentUserPersonalDetails();
     const currentUserAccountIDParam = currentUserPersonalDetails.accountID;
@@ -155,12 +162,12 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
                         <MoneyRequestView
                             allReports={allReports}
                             expensePolicy={policy}
-                            sourceTransactionPolicy={sourceTransactionPolicy}
                             report={targetTransactionThreadReport}
                             shouldShowAnimatedBackground={false}
                             readonly
                             updatedTransaction={mergedTransactionData as unknown as OnyxEntry<Transaction>}
                             mergeTransactionID={transactionID}
+                            taxName={taxName}
                         />
                     </ShowContextMenuContext.Provider>
                 </ScrollView>

From 90bec5bae51e80cd280ccbf29c0b0123c1739906 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 5 Dec 2025 02:41:31 +0700
Subject: [PATCH 31/54] handle selectedTransactionByField in auto merge case

---
 src/libs/MergeTransactionUtils.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index a13d340fe781..23dd7ae3874a 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -209,7 +209,7 @@ function getMergeableDataAndConflictFields(
     localeCompare: (a: string, b: string) => number,
 ) {
     const conflictFields: string[] = [];
-    const mergeableData: Record<string, unknown> = {};
+    const mergeableData: Record<string, unknown> = {selectedTransactionByField: {}};
 
     const targetTransactionDetails = getTransactionDetails(targetTransaction);
     const sourceTransactionDetails = getTransactionDetails(sourceTransaction);
@@ -296,6 +296,9 @@ function getMergeableDataAndConflictFields(
                 mergeTransaction: mergeableData as MergeTransaction,
             });
             Object.assign(mergeableData, updatedValues);
+            if (updatedValues[field]) {
+                (mergeableData.selectedTransactionByField as Record<string, string | undefined>)[field] = selectedTransaction?.transactionID;
+            }
         } else {
             conflictFields.push(field);
         }

From 4c3c29ee6e14bcef1abbc4920f614ad05342a090 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 5 Dec 2025 02:56:28 +0700
Subject: [PATCH 32/54] fix unit test

---
 src/libs/MergeTransactionUtils.ts       | 2 +-
 tests/unit/MergeTransactionUtilsTest.ts | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 23dd7ae3874a..4c2f949c406d 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -296,7 +296,7 @@ function getMergeableDataAndConflictFields(
                 mergeTransaction: mergeableData as MergeTransaction,
             });
             Object.assign(mergeableData, updatedValues);
-            if (updatedValues[field]) {
+            if (!isEmptyMergeValue(updatedValues[field])) {
                 (mergeableData.selectedTransactionByField as Record<string, string | undefined>)[field] = selectedTransaction?.transactionID;
             }
         } else {
diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index a7f40406063a..4bff796556ed 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -379,6 +379,12 @@ describe('MergeTransactionUtils', () => {
                 tag: 'Same Tag',
                 billable: false,
                 attendees: [],
+                selectedTransactionByField: {
+                    merchant: targetTransaction.transactionID,
+                    category: targetTransaction.transactionID,
+                    tag: sourceTransaction.transactionID,
+                    billable: sourceTransaction.transactionID,
+                },
             });
         });
 

From 86db0f395993ce24535dd5f9d97de93ee87a93f4 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 5 Dec 2025 03:10:03 +0700
Subject: [PATCH 33/54] fix unit test

---
 tests/unit/MergeTransactionUtilsTest.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 4bff796556ed..14e63be88499 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -383,7 +383,7 @@ describe('MergeTransactionUtils', () => {
                     merchant: targetTransaction.transactionID,
                     category: targetTransaction.transactionID,
                     tag: sourceTransaction.transactionID,
-                    billable: sourceTransaction.transactionID,
+                    billable: targetTransaction.transactionID,
                 },
             });
         });

From a7cb2bbc7d555631bcb4b05e25d5b7c4fad5f0a7 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 22 Dec 2025 19:00:51 +0700
Subject: [PATCH 34/54] fix tax push row missing description

---
 src/components/MoneyRequestConfirmationListFooter.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx
index b49a1555b6ba..b9c7229abc50 100644
--- a/src/components/MoneyRequestConfirmationListFooter.tsx
+++ b/src/components/MoneyRequestConfirmationListFooter.tsx
@@ -359,7 +359,7 @@ function MoneyRequestConfirmationListFooter({
     // since the destination is already determined and there's no need to show a selectable list.
     const shouldReportBeEditable = (isFromGlobalCreate ? shouldReportBeEditableFromFAB : availableOutstandingReports.length > 1) && !isMoneyRequestReport(reportID, allReports);
 
-    const taxRates = policy?.taxRates ?? null;
+    const taxRatesName = policy?.taxRates?.name ?? CONST.DEFAULT_TAX.name;
     // In Send Money and Split Bill with Scan flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item
     const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest;
     // Determines whether the tax fields can be modified.
@@ -682,10 +682,10 @@ function MoneyRequestConfirmationListFooter({
         {
             item: (
                 <MenuItemWithTopDescription
-                    key={`${taxRates?.name}${taxRateTitle}`}
+                    key={`${taxRatesName}${taxRateTitle}`}
                     shouldShowRightIcon={canModifyTaxFields}
                     title={taxRateTitle}
-                    description={taxRates?.name}
+                    description={taxRatesName}
                     style={[styles.moneyRequestMenuItem]}
                     titleStyle={styles.flex1}
                     onPress={() => {
@@ -704,7 +704,7 @@ function MoneyRequestConfirmationListFooter({
         {
             item: (
                 <MenuItemWithTopDescription
-                    key={`${taxRates?.name}${formattedTaxAmount}`}
+                    key={`${taxRatesName}${formattedTaxAmount}`}
                     shouldShowRightIcon={canModifyTaxFields}
                     title={formattedTaxAmount}
                     description={translate('iou.taxAmount')}

From 27557e488280af139a847e7fa17de7cdad29cf08 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 22 Dec 2025 19:02:55 +0700
Subject: [PATCH 35/54] fix: tax is auto merged even when target transaction
 does not have tax enabled

---
 src/libs/MergeTransactionUtils.ts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index e2b15c996f53..149994a16383 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -286,6 +286,9 @@ function getMergeableDataAndConflictFields(
         }
 
         if (isTargetValueEmpty || isSourceValueEmpty || targetValue === sourceValue) {
+            if (field === 'taxValue' && isTargetValueEmpty) {
+                continue;
+            }
             const selectedTransaction = isTargetValueEmpty ? sourceTransaction : targetTransaction;
             const selectedFieldValue = isTargetValueEmpty ? sourceValue : targetValue;
             const updatedValues = getMergeFieldUpdatedValues({

From 027d7371f4f72ceabe3742a268380468bb13f60d Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 29 Dec 2025 19:00:57 +0700
Subject: [PATCH 36/54] lint

---
 src/libs/MergeTransactionUtils.ts              | 10 ++++++++--
 .../TransactionMerge/DetailsReviewPage.tsx     | 18 ++++++++++++++++--
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 1431eb33e94a..884dc357a50e 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -297,7 +297,7 @@ function getMergeableDataAndConflictFields(
             const updatedValues = getMergeFieldUpdatedValues({
                 transaction: selectedTransaction,
                 field,
-                fieldValue: selectedFieldValue as MergeTransaction[typeof field], searchReports,
+                fieldValue: selectedFieldValue as MergeTransaction[typeof field],
                 mergeTransaction: mergeableData as MergeTransaction,
                 searchReports,
             });
@@ -566,7 +566,13 @@ type GetMergeFieldUpdatedValuesParams<K extends MergeFieldKey> = {
  * Build updated values for merge transaction field selection
  * Handles special cases like currency for amount field, reportID, taxValue
  */
-function getMergeFieldUpdatedValues<K extends MergeFieldKey>({transaction, field, fieldValue, mergeTransaction, searchReports}: GetMergeFieldUpdatedValuesParams<K>): MergeTransactionUpdateValues {
+function getMergeFieldUpdatedValues<K extends MergeFieldKey>({
+    transaction,
+    field,
+    fieldValue,
+    mergeTransaction,
+    searchReports,
+}: GetMergeFieldUpdatedValuesParams<K>): MergeTransactionUpdateValues {
     const updatedValues: MergeTransactionUpdateValues = {
         [field]: fieldValue,
     };
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index daf642be2053..f39315fb3426 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -114,8 +114,22 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
 
     // Build merge fields array with all necessary information
     const mergeFields = useMemo(
-        () => buildMergeFieldsData(conflictFields, targetTransaction, sourceTransaction, mergeTransaction, targetTransactionPolicy, sourceTransactionPolicy, translate, [targetTransactionReport, sourceTransactionReport]),
-        [conflictFields, targetTransaction, sourceTransaction, mergeTransaction, targetTransactionReport, sourceTransactionReport, targetTransactionPolicy, sourceTransactionPolicy, translate],
+        () =>
+            buildMergeFieldsData(conflictFields, targetTransaction, sourceTransaction, mergeTransaction, targetTransactionPolicy, sourceTransactionPolicy, translate, [
+                targetTransactionReport,
+                sourceTransactionReport,
+            ]),
+        [
+            conflictFields,
+            targetTransaction,
+            sourceTransaction,
+            mergeTransaction,
+            targetTransactionReport,
+            sourceTransactionReport,
+            targetTransactionPolicy,
+            sourceTransactionPolicy,
+            translate,
+        ],
     );
 
     // If this screen has multiple "selection cards" on it and the user skips one or more, show an error above the footer button

From 496e727851cbb8025e533cffad16e85560dc655f Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 29 Dec 2025 19:10:28 +0700
Subject: [PATCH 37/54] lint

---
 src/libs/MergeTransactionUtils.ts    | 3 ++-
 src/libs/actions/MergeTransaction.ts | 1 -
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 884dc357a50e..5e3316cd2aab 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -464,6 +464,7 @@ function selectTargetAndSourceTransactionsForMerge(targetTransaction: OnyxEntry<
  * Get display value for merge transaction field
  * @param field - The merge field key to get display value for
  * @param transaction - The transaction to get the field value from
+ * @param policy - The policy that the transaction belongs to
  * @param translate - The translation function
  * @returns The formatted display string for the field value
  */
@@ -564,7 +565,7 @@ type GetMergeFieldUpdatedValuesParams<K extends MergeFieldKey> = {
 
 /**
  * Build updated values for merge transaction field selection
- * Handles special cases like currency for amount field, reportID, taxValue
+ * Handles special cases like currency for amount field, report name, tax value and additional fields for distance requests
  */
 function getMergeFieldUpdatedValues<K extends MergeFieldKey>({
     transaction,
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index c7f247e61468..419d75f276c8 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -11,7 +11,6 @@ import {
     getMergeableDataAndConflictFields,
     getMergeFieldValue,
     getTransactionThreadReportID,
-    MERGE_FIELDS,
     selectTargetAndSourceTransactionsForMerge,
     shouldNavigateToReceiptReview,
 } from '@libs/MergeTransactionUtils';

From 0d8581f5812cd8d55d684efab49885ce514a63d7 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 29 Dec 2025 19:12:57 +0700
Subject: [PATCH 38/54] get sourceTransactionReport from useMergeTransactions

---
 src/pages/TransactionMerge/ConfirmationPage.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 0eaa2ea5557a..9eb072f12c19 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -39,14 +39,13 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
 
     const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
     const [mergeTransaction, mergeTransactionMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${transactionID}`, {canBeMissing: true});
-    const {targetTransaction, sourceTransaction, targetTransactionReport} = useMergeTransactions({mergeTransaction});
+    const {targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport} = useMergeTransactions({mergeTransaction});
 
     const policyID = targetTransactionReport?.policyID;
     const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true});
     const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
     const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true});
 
-    const sourceTransactionReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction?.reportID}`];
     const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`, {canBeMissing: true});
     const taxName =
         mergeTransaction?.taxValue &&

From 86d0b4cdc0f5e8df35052a242e478fdd1565a601 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 1 Jan 2026 01:15:29 +0700
Subject: [PATCH 39/54] use snapshot policy data

---
 src/hooks/useMergeTransactions.ts               | 17 +++++++++++++++--
 src/pages/TransactionMerge/ConfirmationPage.tsx | 16 +++++++---------
 .../TransactionMerge/DetailsReviewPage.tsx      |  6 +++---
 3 files changed, 25 insertions(+), 14 deletions(-)

diff --git a/src/hooks/useMergeTransactions.ts b/src/hooks/useMergeTransactions.ts
index d33eb28b3f80..aa1db52f8265 100644
--- a/src/hooks/useMergeTransactions.ts
+++ b/src/hooks/useMergeTransactions.ts
@@ -4,7 +4,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import {getTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {MergeTransaction, Report, SearchResults, Transaction} from '@src/types/onyx';
+import type {MergeTransaction, Policy, Report, SearchResults, Transaction} from '@src/types/onyx';
 import useOnyx from './useOnyx';
 
 type UseMergeTransactionsProps = {
@@ -16,6 +16,8 @@ type UseMergeTransactionsReturn = {
     sourceTransaction?: Transaction;
     targetTransactionReport?: Report;
     sourceTransactionReport?: Report;
+    targetTransactionPolicy?: Policy;
+    sourceTransactionPolicy?: Policy;
 };
 
 function getTransaction(
@@ -57,11 +59,20 @@ function useMergeTransactions({mergeTransaction}: UseMergeTransactionsProps): Us
     let [sourceTransactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceTransaction?.reportID)}`, {
         canBeMissing: true,
     });
+    let [targetTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(targetTransactionReport?.policyID)}`, {
+        canBeMissing: true,
+    });
+    let [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(sourceTransactionReport?.policyID)}`, {
+        canBeMissing: true,
+    });
 
-    // If we're on search and main collection reports are not available, get them from the search snapshot
     if (searchHash && currentSearchResults?.data) {
+        // If we're on search and main collection reports are not available, get them from the search snapshot
         targetTransactionReport = targetTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${targetTransaction?.reportID}`];
         sourceTransactionReport = sourceTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction?.reportID}`];
+        // If we're on search, search snapshot policies are more up to date
+        targetTransactionPolicy = currentSearchResults?.data[`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionReport?.policyID}`];
+        sourceTransactionPolicy = currentSearchResults?.data[`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`];
     }
 
     return {
@@ -69,6 +80,8 @@ function useMergeTransactions({mergeTransaction}: UseMergeTransactionsProps): Us
         sourceTransaction,
         targetTransactionReport,
         sourceTransactionReport,
+        targetTransactionPolicy,
+        sourceTransactionPolicy,
     };
 }
 
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 1b446c1f89cc..8382b4565860 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -40,18 +40,16 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
 
     const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
     const [mergeTransaction, mergeTransactionMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true});
-    const {targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport} = useMergeTransactions({mergeTransaction});
+    const {targetTransaction, sourceTransaction, targetTransactionReport, targetTransactionPolicy, sourceTransactionPolicy} = useMergeTransactions({mergeTransaction});
 
-    const policyID = targetTransactionReport?.policyID;
-    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true});
-    const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
-    const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true});
+    const targetPolicyID = targetTransactionPolicy?.id;
+    const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${targetPolicyID}`, {canBeMissing: true});
+    const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${targetPolicyID}`, {canBeMissing: true});
 
-    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`, {canBeMissing: true});
     const taxName =
         mergeTransaction?.taxValue &&
         getTaxName(
-            mergeTransaction?.selectedTransactionByField?.taxValue === mergeTransaction?.sourceTransactionID ? sourceTransactionPolicy : policy,
+            mergeTransaction?.selectedTransactionByField?.taxValue === mergeTransaction?.sourceTransactionID ? sourceTransactionPolicy : targetTransactionPolicy,
             mergeTransaction as unknown as OnyxEntry<Transaction>,
         );
 
@@ -76,7 +74,7 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
             mergeTransaction,
             targetTransaction,
             sourceTransaction,
-            policy,
+            policy: targetTransactionPolicy,
             policyTags,
             policyCategories,
             currentUserAccountIDParam,
@@ -117,7 +115,7 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
                     </View>
                     <MoneyRequestView
                         allReports={allReports}
-                        expensePolicy={policy}
+                        expensePolicy={targetTransactionPolicy}
                         parentReportID={targetTransactionReport?.reportID}
                         shouldShowAnimatedBackground={false}
                         readonly
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index 82c1dee58688..fac7b176e307 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -44,10 +44,10 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
     const {transactionID, isOnSearch, backTo} = route.params;
 
     const [mergeTransaction, mergeTransactionMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true});
-    const {targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport} = useMergeTransactions({mergeTransaction});
+    const {targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport, targetTransactionPolicy, sourceTransactionPolicy} = useMergeTransactions({
+        mergeTransaction,
+    });
 
-    const [targetTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionReport?.policyID}`, {canBeMissing: true});
-    const [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`, {canBeMissing: true});
     const [hasErrors, setHasErrors] = useState<Partial<Record<MergeFieldKey, boolean>>>({});
     const [conflictFields, setConflictFields] = useState<MergeFieldKey[]>([]);
 

From 0e599880940bf3720bfcfd13f1c852db1c11346b Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 1 Jan 2026 01:37:55 +0700
Subject: [PATCH 40/54] Revert "fix tax push row missing description"

This reverts commit a7cb2bbc7d555631bcb4b05e25d5b7c4fad5f0a7.
---
 src/components/MoneyRequestConfirmationListFooter.tsx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx
index b9c7229abc50..b49a1555b6ba 100644
--- a/src/components/MoneyRequestConfirmationListFooter.tsx
+++ b/src/components/MoneyRequestConfirmationListFooter.tsx
@@ -359,7 +359,7 @@ function MoneyRequestConfirmationListFooter({
     // since the destination is already determined and there's no need to show a selectable list.
     const shouldReportBeEditable = (isFromGlobalCreate ? shouldReportBeEditableFromFAB : availableOutstandingReports.length > 1) && !isMoneyRequestReport(reportID, allReports);
 
-    const taxRatesName = policy?.taxRates?.name ?? CONST.DEFAULT_TAX.name;
+    const taxRates = policy?.taxRates ?? null;
     // In Send Money and Split Bill with Scan flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item
     const shouldShowDate = shouldShowSmartScanFields || isDistanceRequest;
     // Determines whether the tax fields can be modified.
@@ -682,10 +682,10 @@ function MoneyRequestConfirmationListFooter({
         {
             item: (
                 <MenuItemWithTopDescription
-                    key={`${taxRatesName}${taxRateTitle}`}
+                    key={`${taxRates?.name}${taxRateTitle}`}
                     shouldShowRightIcon={canModifyTaxFields}
                     title={taxRateTitle}
-                    description={taxRatesName}
+                    description={taxRates?.name}
                     style={[styles.moneyRequestMenuItem]}
                     titleStyle={styles.flex1}
                     onPress={() => {
@@ -704,7 +704,7 @@ function MoneyRequestConfirmationListFooter({
         {
             item: (
                 <MenuItemWithTopDescription
-                    key={`${taxRatesName}${formattedTaxAmount}`}
+                    key={`${taxRates?.name}${formattedTaxAmount}`}
                     shouldShowRightIcon={canModifyTaxFields}
                     title={formattedTaxAmount}
                     description={translate('iou.taxAmount')}

From 7d56278f2655b920b465009b459100f8353b5bfb Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 6 Jan 2026 00:45:36 +0700
Subject: [PATCH 41/54] update transaction report ID based on selection

---
 src/libs/TransactionUtils/index.ts   |  4 +++
 src/libs/actions/MergeTransaction.ts | 40 +++++++++++++---------------
 2 files changed, 23 insertions(+), 21 deletions(-)

diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 59a9b7d19204..a600762775a0 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -670,6 +670,10 @@ function getUpdatedTransaction({
         updatedTransaction.tag = transactionChanges.tag;
     }
 
+    if (Object.hasOwn(transactionChanges, 'reportID') && typeof transactionChanges.reportID === 'string') {
+        updatedTransaction.reportID = transactionChanges.reportID;
+    }
+
     if (Object.hasOwn(transactionChanges, 'attendees')) {
         updatedTransaction.comment = {
             ...updatedTransaction.comment,
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 419d75f276c8..a53d1ad33a06 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -339,28 +339,26 @@ function mergeTransactionRequest({
         value: sourceTransaction,
     };
     const transactionsOfSourceReport = getReportTransactions(sourceTransaction.reportID);
-    const optimisticSourceReportData: OnyxUpdate[] =
-        transactionsOfSourceReport.length === 1
-            ? [
-                  {
-                      onyxMethod: Onyx.METHOD.SET,
-                      key: `${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction.reportID}`,
-                      value: null,
-                  },
-              ]
-            : [];
-
+    const shouldDeleteSourceReport = transactionsOfSourceReport.length === 1 && mergeTransaction.reportID !== sourceTransaction.reportID;
+    const optimisticSourceReportData: OnyxUpdate[] = shouldDeleteSourceReport
+        ? [
+              {
+                  onyxMethod: Onyx.METHOD.SET,
+                  key: `${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction.reportID}`,
+                  value: null,
+              },
+          ]
+        : [];
     // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830
-    const failureSourceReportData: OnyxUpdate[] =
-        transactionsOfSourceReport.length === 1
-            ? [
-                  {
-                      onyxMethod: Onyx.METHOD.SET,
-                      key: `${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction.reportID}`,
-                      value: getReportOrDraftReport(sourceTransaction.reportID),
-                  },
-              ]
-            : [];
+    const failureSourceReportData: OnyxUpdate[] = shouldDeleteSourceReport
+        ? [
+              {
+                  onyxMethod: Onyx.METHOD.SET,
+                  key: `${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction.reportID}`,
+                  value: getReportOrDraftReport(sourceTransaction.reportID),
+              },
+          ]
+        : [];
     const iouActionOfSourceTransaction = getIOUActionForReportID(sourceTransaction.reportID, sourceTransaction.transactionID);
     const optimisticSourceReportActionData: OnyxUpdate[] = iouActionOfSourceTransaction
         ? [

From 6328a3be85b8b032689b4897fb6110de0dc55426 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 8 Jan 2026 23:06:50 +0700
Subject: [PATCH 42/54] Revert "fallback tax rate"

This reverts commit d14c2b445b7a5f18580696b729c8cb2759af8851.
---
 src/components/ReportActionItem/MoneyRequestView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index ded86807ab50..4a614b05fe79 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -310,7 +310,7 @@ function MoneyRequestView({
     const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction);
 
     const actualTransactionDate = isFromMergeTransaction && updatedTransaction ? getFormattedCreated(updatedTransaction) : transactionDate;
-    const fallbackTaxRateTitle = updatedTransaction?.taxValue ?? transaction?.taxValue;
+    const fallbackTaxRateTitle = transaction?.taxValue;
 
     const isSettled = isSettledReportUtils(moneyRequestReport);
     const isCancelled = moneyRequestReport && moneyRequestReport?.isCancelledIOU;

From 6518595332e242ac4984d412be169c9881486ce1 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Thu, 8 Jan 2026 23:18:38 +0700
Subject: [PATCH 43/54] fix: do not reset selectedTransactionByField

---
 src/libs/MergeTransactionUtils.ts | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 9165ef637d87..46828900ac7f 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -211,7 +211,8 @@ function getMergeableDataAndConflictFields(
     searchReports: Array<OnyxEntry<Report>> = [],
 ) {
     const conflictFields: string[] = [];
-    const mergeableData: Record<string, unknown> = {selectedTransactionByField: {}};
+    const mergeableData: Record<string, unknown> = {};
+    const selectedTransactionByField: Record<string, string | undefined> = {};
 
     const targetTransactionDetails = getTransactionDetails(targetTransaction);
     const sourceTransactionDetails = getTransactionDetails(sourceTransaction);
@@ -303,13 +304,17 @@ function getMergeableDataAndConflictFields(
             });
             Object.assign(mergeableData, updatedValues);
             if (!isEmptyMergeValue(updatedValues[field])) {
-                (mergeableData.selectedTransactionByField as Record<string, string | undefined>)[field] = selectedTransaction?.transactionID;
+                selectedTransactionByField[field] = selectedTransaction?.transactionID;
             }
         } else {
             conflictFields.push(field);
         }
     }
 
+    if (Object.keys(selectedTransactionByField).length > 0) {
+        mergeableData.selectedTransactionByField = selectedTransactionByField;
+    }
+
     return {mergeableData, conflictFields};
 }
 

From b9099215c95ef57c412265ab2a3e4ed0695b0b2d Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Wed, 14 Jan 2026 23:57:34 +0700
Subject: [PATCH 44/54] fix lint

---
 src/libs/actions/MergeTransaction.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 2cdd42928cd1..70eeed67b1a3 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -10,7 +10,6 @@ import {
     DERIVED_MERGE_FIELDS,
     getMergeableDataAndConflictFields,
     getMergeFieldValue,
-    getTransactionThreadReportID,
     selectTargetAndSourceTransactionsForMerge,
     shouldNavigateToReceiptReview,
 } from '@libs/MergeTransactionUtils';

From f12a9352f1c5b59a269713adcc66c803f8a3b756 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 16 Jan 2026 00:47:05 +0700
Subject: [PATCH 45/54] compute taxName on selection

---
 .../ReportActionItem/MoneyRequestView.tsx     |  8 +---
 src/hooks/useSelectedTransactionsActions.ts   | 11 ++++-
 src/libs/DebugUtils.ts                        |  1 +
 src/libs/MergeTransactionUtils.ts             | 40 ++++++++++++-------
 src/libs/actions/MergeTransaction.ts          | 17 +++++++-
 src/pages/Search/SearchPage.tsx               |  2 +-
 .../TransactionMerge/ConfirmationPage.tsx     | 16 ++------
 .../TransactionMerge/DetailsReviewPage.tsx    | 25 ++++++++----
 .../MergeTransactionsListContent.tsx          | 16 +++++---
 .../TransactionMerge/ReceiptReviewPage.tsx    |  4 +-
 src/types/onyx/MergeTransaction.ts            |  3 ++
 src/types/onyx/Transaction.ts                 |  3 ++
 tests/utils/collections/mergeTransaction.ts   |  1 +
 13 files changed, 95 insertions(+), 52 deletions(-)

diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index b2a8940cf260..30d51e960a3c 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -135,9 +135,6 @@ type MoneyRequestViewProps = {
 
     /** Merge transaction ID to show in merge transaction flow */
     mergeTransactionID?: string;
-
-    /** Tax name to display in merge transaction flow */
-    taxName?: string;
 };
 
 const perDiemPoliciesSelector = (policies: OnyxCollection<OnyxTypes.Policy>) => {
@@ -161,7 +158,6 @@ function MoneyRequestView({
     updatedTransaction,
     isFromReviewDuplicates = false,
     mergeTransactionID,
-    taxName,
 }: MoneyRequestViewProps) {
     const icons = useMemoizedLazyExpensifyIcons(['DotIndicator', 'Checkmark', 'Suitcase']);
     const styles = useThemeStyles();
@@ -378,7 +374,7 @@ function MoneyRequestView({
     const canEditReimbursable = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.REIMBURSABLE, undefined, isChatReportArchived);
     const shouldShowAttendees = shouldShowAttendeesTransactionUtils(iouType, policy);
 
-    const shouldShowTax = !!taxName || isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest, isPerDiemRequest, isTimeRequest);
+    const shouldShowTax = !!transaction?.taxName || isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest, isPerDiemRequest, isTimeRequest);
     const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID);
     const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID;
 
@@ -548,7 +544,7 @@ function MoneyRequestView({
     const decodedCategoryName = getDecodedCategoryName(categoryValue);
     const categoryCopyValue = !canEdit ? decodedCategoryName : undefined;
     const cardCopyValue = cardProgramName;
-    const taxRateValue = taxName ?? taxRateTitle ?? fallbackTaxRateTitle;
+    const taxRateValue = transaction?.taxName ?? taxRateTitle ?? fallbackTaxRateTitle;
     const taxRateCopyValue = !canEditTaxFields ? taxRateValue : undefined;
     const taxAmountTitle = formattedTaxAmount ? formattedTaxAmount.toString() : '';
     const taxAmountCopyValue = !canEditTaxFields ? taxAmountTitle : undefined;
diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts
index 6a640914a309..c0b61bf1a4ab 100644
--- a/src/hooks/useSelectedTransactionsActions.ts
+++ b/src/hooks/useSelectedTransactionsActions.ts
@@ -310,7 +310,16 @@ function useSelectedTransactionsActions({
                     text: translate('common.merge'),
                     icon: expensifyIcons.ArrowCollapse,
                     value: MERGE,
-                    onSelected: () => setupMergeTransactionDataAndNavigate(transactionID, selectedTransactionsList, localeCompare, [], false, isOnSearch),
+                    onSelected: () =>
+                        setupMergeTransactionDataAndNavigate(
+                            transactionID,
+                            selectedTransactionsList,
+                            localeCompare,
+                            [],
+                            false,
+                            isOnSearch,
+                            Array(selectedTransactionsList.length).fill(policy),
+                        ),
                 });
             }
         }
diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts
index 8d7af62b6891..38f25b3ec385 100644
--- a/src/libs/DebugUtils.ts
+++ b/src/libs/DebugUtils.ts
@@ -1112,6 +1112,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
                     isDemoTransaction: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     splitExpensesTotal: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     taxValue: CONST.RED_BRICK_ROAD_PENDING_ACTION,
+                    taxName: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     pendingAutoCategorizationTime: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     groupAmount: CONST.RED_BRICK_ROAD_PENDING_ACTION,
                     groupCurrency: CONST.RED_BRICK_ROAD_PENDING_ACTION,
diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 46828900ac7f..eac6251c8c9c 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -201,7 +201,9 @@ function getTransactionsAndReportsFromSearch(
  * @param targetTransaction - The target transaction
  * @param sourceTransaction - The source transaction
  * @param localeCompare - The localize compare function
- * @param localeCompare - The localize compare function
+ * @param searchReports - The search reports to use for report name lookup
+ * @param targetTransactionPolicy - The policy of the target transaction
+ * @param sourceTransactionPolicy - The policy of the source transaction
  * @returns mergeableData and conflictFields
  */
 function getMergeableDataAndConflictFields(
@@ -209,10 +211,11 @@ function getMergeableDataAndConflictFields(
     sourceTransaction: OnyxEntry<Transaction>,
     localeCompare: LocaleContextProps['localeCompare'],
     searchReports: Array<OnyxEntry<Report>> = [],
+    targetTransactionPolicy?: OnyxEntry<Policy>,
+    sourceTransactionPolicy?: OnyxEntry<Policy>,
 ) {
     const conflictFields: string[] = [];
     const mergeableData: Record<string, unknown> = {};
-    const selectedTransactionByField: Record<string, string | undefined> = {};
 
     const targetTransactionDetails = getTransactionDetails(targetTransaction);
     const sourceTransactionDetails = getTransactionDetails(sourceTransaction);
@@ -295,26 +298,20 @@ function getMergeableDataAndConflictFields(
             }
             const selectedTransaction = isTargetValueEmpty ? sourceTransaction : targetTransaction;
             const selectedFieldValue = isTargetValueEmpty ? sourceValue : targetValue;
+            const selectedPolicy = isTargetValueEmpty ? sourceTransactionPolicy : targetTransactionPolicy;
             const updatedValues = getMergeFieldUpdatedValues({
                 transaction: selectedTransaction,
                 field,
                 fieldValue: selectedFieldValue as MergeTransaction[typeof field],
                 mergeTransaction: mergeableData as MergeTransaction,
                 searchReports,
+                policy: selectedPolicy,
             });
             Object.assign(mergeableData, updatedValues);
-            if (!isEmptyMergeValue(updatedValues[field])) {
-                selectedTransactionByField[field] = selectedTransaction?.transactionID;
-            }
         } else {
             conflictFields.push(field);
         }
     }
-
-    if (Object.keys(selectedTransactionByField).length > 0) {
-        mergeableData.selectedTransactionByField = selectedTransactionByField;
-    }
-
     return {mergeableData, conflictFields};
 }
 
@@ -453,16 +450,28 @@ function areTransactionsEligibleForMerge(transaction1: OnyxEntry<Transaction>, t
  *
  * @param targetTransaction - The transaction where the merge action is started from
  * @param sourceTransaction - The selected transaction to be merged with the target transaction
- * @returns An object containing the determined targetTransaction and sourceTransaction
+ * @param targetTransactionPolicy - The policy of the target transaction
+ * @param sourceTransactionPolicy - The policy of the source transaction
+ * @returns An object containing the determined targetTransaction and sourceTransaction, targetTransactionPolicy and sourceTransactionPolicy
  */
-function selectTargetAndSourceTransactionsForMerge(targetTransaction: OnyxEntry<Transaction>, sourceTransaction: OnyxEntry<Transaction>) {
+function selectTargetAndSourceTransactionsForMerge(
+    targetTransaction: OnyxEntry<Transaction>,
+    sourceTransaction: OnyxEntry<Transaction>,
+    targetTransactionPolicy?: Policy,
+    sourceTransactionPolicy?: Policy,
+) {
     // If target transaction is a card or split expense, always preserve the target transaction
     // Card takes precedence over split expense
     if (isFromCreditCardImport(sourceTransaction) || (isExpenseSplit(sourceTransaction) && !isFromCreditCardImport(targetTransaction))) {
-        return {targetTransaction: sourceTransaction, sourceTransaction: targetTransaction};
+        return {
+            targetTransaction: sourceTransaction,
+            sourceTransaction: targetTransaction,
+            targetTransactionPolicy: sourceTransactionPolicy,
+            sourceTransactionPolicy: targetTransactionPolicy,
+        };
     }
 
-    return {targetTransaction, sourceTransaction};
+    return {targetTransaction, sourceTransaction, targetTransactionPolicy, sourceTransactionPolicy};
 }
 
 /**
@@ -566,6 +575,7 @@ type GetMergeFieldUpdatedValuesParams<K extends MergeFieldKey> = {
     fieldValue: MergeTransaction[K];
     mergeTransaction?: OnyxEntry<MergeTransaction>;
     searchReports?: Array<OnyxEntry<Report>>;
+    policy?: OnyxEntry<Policy>;
 };
 
 /**
@@ -578,6 +588,7 @@ function getMergeFieldUpdatedValues<K extends MergeFieldKey>({
     fieldValue,
     mergeTransaction,
     searchReports,
+    policy,
 }: GetMergeFieldUpdatedValuesParams<K>): MergeTransactionUpdateValues {
     const updatedValues: MergeTransactionUpdateValues = {
         [field]: fieldValue,
@@ -609,6 +620,7 @@ function getMergeFieldUpdatedValues<K extends MergeFieldKey>({
 
     if (field === 'taxValue') {
         updatedValues.taxCode = transaction?.taxCode;
+        updatedValues.taxName = getTaxName(policy, transaction) ?? transaction?.taxValue ?? '';
         if (mergeTransaction?.amount) {
             updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(transaction?.taxValue, mergeTransaction.amount, mergeTransaction.currency));
         }
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 70eeed67b1a3..5f64ff125659 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -47,6 +47,7 @@ function setupMergeTransactionDataAndNavigate(
     searchReports?: Report[],
     isSelectingSourceTransaction?: boolean,
     isOnSearch?: boolean,
+    policies?: OnyxEntry<Policy>[],
 ) {
     if (!transactions.length || transactions.length > 2) {
         return;
@@ -61,7 +62,12 @@ function setupMergeTransactionDataAndNavigate(
         }
     }
 
-    const {targetTransaction, sourceTransaction} = selectTargetAndSourceTransactionsForMerge(transactions.at(0), transactions.at(1));
+    const {targetTransaction, sourceTransaction, targetTransactionPolicy, sourceTransactionPolicy} = selectTargetAndSourceTransactionsForMerge(
+        transactions.at(0),
+        transactions.at(1),
+        policies?.at(0),
+        policies?.at(1),
+    );
     if (!targetTransaction || !sourceTransaction) {
         return;
     }
@@ -82,7 +88,14 @@ function setupMergeTransactionDataAndNavigate(
         }
 
         // If transactions are identical, skip to the confirmation page
-        const {conflictFields, mergeableData} = getMergeableDataAndConflictFields(targetTransaction, sourceTransaction, localeCompare, searchReports);
+        const {conflictFields, mergeableData} = getMergeableDataAndConflictFields(
+            targetTransaction,
+            sourceTransaction,
+            localeCompare,
+            searchReports,
+            targetTransactionPolicy,
+            sourceTransactionPolicy,
+        );
         if (!conflictFields.length) {
             // If there are no conflict fields, we should set mergeable data and navigate to the confirmation page
             setMergeTransactionKey(navigationTransactionID, mergeableData);
diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx
index 4207312b6934..d403e8bbea4f 100644
--- a/src/pages/Search/SearchPage.tsx
+++ b/src/pages/Search/SearchPage.tsx
@@ -917,7 +917,7 @@ function SearchPage({route}: SearchPageProps) {
                         text: translate('common.merge'),
                         icon: expensifyIcons.ArrowCollapse,
                         value: CONST.SEARCH.BULK_ACTION_TYPES.MERGE,
-                        onSelected: () => setupMergeTransactionDataAndNavigate(transactionID, transactions, localeCompare, reports, false, true),
+                        onSelected: () => setupMergeTransactionDataAndNavigate(transactionID, transactions, localeCompare, reports, false, true, transactionPolicies),
                     });
                 }
             }
diff --git a/src/pages/TransactionMerge/ConfirmationPage.tsx b/src/pages/TransactionMerge/ConfirmationPage.tsx
index 811e9c177f8e..f2274fbe87e4 100644
--- a/src/pages/TransactionMerge/ConfirmationPage.tsx
+++ b/src/pages/TransactionMerge/ConfirmationPage.tsx
@@ -23,7 +23,6 @@ import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTop
 import Navigation from '@libs/Navigation/Navigation';
 import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
 import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types';
-import {getTaxName} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -42,21 +41,13 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
 
     const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
     const [mergeTransaction, mergeTransactionMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true});
-    const {targetTransaction, sourceTransaction, targetTransactionReport, targetTransactionPolicy, sourceTransactionPolicy} = useMergeTransactions({mergeTransaction});
+    const {targetTransaction, sourceTransaction, targetTransactionReport, targetTransactionPolicy} = useMergeTransactions({mergeTransaction});
     const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {
         canBeMissing: false,
     });
 
-    const targetPolicyID = targetTransactionPolicy?.id;
-    const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${targetPolicyID}`, {canBeMissing: true});
-    const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${targetPolicyID}`, {canBeMissing: true});
-
-    const taxName =
-        mergeTransaction?.taxValue &&
-        getTaxName(
-            mergeTransaction?.selectedTransactionByField?.taxValue === mergeTransaction?.sourceTransactionID ? sourceTransactionPolicy : targetTransactionPolicy,
-            mergeTransaction as unknown as OnyxEntry<Transaction>,
-        );
+    const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(targetTransactionPolicy?.id)}`, {canBeMissing: true});
+    const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(targetTransactionPolicy?.id)}`, {canBeMissing: true});
 
     const currentUserPersonalDetails = useCurrentUserPersonalDetails();
     const currentUserAccountIDParam = currentUserPersonalDetails.accountID;
@@ -144,7 +135,6 @@ function ConfirmationPage({route}: ConfirmationPageProps) {
                         readonly
                         updatedTransaction={mergedTransactionData as unknown as OnyxEntry<Transaction>}
                         mergeTransactionID={transactionID}
-                        taxName={taxName}
                     />
                 </ScrollView>
                 <FixedFooter style={styles.ph5}>
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index fac7b176e307..b4c272136eed 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -56,14 +56,18 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
             return;
         }
 
-        const {conflictFields: detectedConflictFields, mergeableData} = getMergeableDataAndConflictFields(targetTransaction, sourceTransaction, localeCompare, [
-            targetTransactionReport,
-            sourceTransactionReport,
-        ]);
+        const {conflictFields: detectedConflictFields, mergeableData} = getMergeableDataAndConflictFields(
+            targetTransaction,
+            sourceTransaction,
+            localeCompare,
+            [targetTransactionReport, sourceTransactionReport],
+            targetTransactionPolicy,
+            sourceTransactionPolicy,
+        );
 
         setMergeTransactionKey(transactionID, mergeableData);
         setConflictFields(detectedConflictFields as MergeFieldKey[]);
-    }, [targetTransaction, sourceTransaction, transactionID, localeCompare, sourceTransactionReport, targetTransactionReport]);
+    }, [targetTransaction, sourceTransaction, transactionID, localeCompare, sourceTransactionReport, targetTransactionReport, targetTransactionPolicy, sourceTransactionPolicy]);
 
     // Handle selection
     const handleSelect = useCallback(
@@ -79,7 +83,14 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
 
             // Update both the field value and track which transaction was selected (persisted in Onyx)
             const currentSelections = mergeTransaction?.selectedTransactionByField ?? {};
-            const updatedValues = getMergeFieldUpdatedValues({transaction, field, fieldValue, mergeTransaction, searchReports: [targetTransactionReport, sourceTransactionReport]});
+            const updatedValues = getMergeFieldUpdatedValues({
+                transaction,
+                field,
+                fieldValue,
+                mergeTransaction,
+                searchReports: [targetTransactionReport, sourceTransactionReport],
+                policy: transaction.transactionID === targetTransaction?.transactionID ? targetTransactionPolicy : sourceTransactionPolicy,
+            });
 
             setMergeTransactionKey(transactionID, {
                 ...updatedValues,
@@ -89,7 +100,7 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
                 } as Partial<Record<MergeFieldKey, string>>,
             });
         },
-        [mergeTransaction, transactionID, targetTransactionReport, sourceTransactionReport],
+        [mergeTransaction, transactionID, targetTransactionReport, sourceTransactionReport, targetTransaction?.transactionID, targetTransactionPolicy, sourceTransactionPolicy],
     );
 
     // Handle continue
diff --git a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
index d15304576461..619588e3cae3 100644
--- a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
+++ b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx
@@ -42,8 +42,9 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr
     const {isOffline} = useNetwork();
 
     const eligibleTransactions = mergeTransaction?.eligibleTransactions;
-    const {targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport} = useMergeTransactions({mergeTransaction});
-    const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionReport?.policyID}`, {canBeMissing: true});
+    const {targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport, targetTransactionPolicy, sourceTransactionPolicy} = useMergeTransactions({
+        mergeTransaction,
+    });
 
     useEffect(() => {
         // If the eligible transactions are already loaded, don't fetch them again
@@ -51,8 +52,8 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr
             return;
         }
 
-        getTransactionsForMerging({isOffline, targetTransaction, transactions, policy, report: targetTransactionReport, currentUserLogin});
-    }, [transactions, isOffline, mergeTransaction?.eligibleTransactions, policy, targetTransactionReport, currentUserLogin, targetTransaction]);
+        getTransactionsForMerging({isOffline, targetTransaction, transactions, policy: targetTransactionPolicy, report: targetTransactionReport, currentUserLogin});
+    }, [transactions, isOffline, mergeTransaction?.eligibleTransactions, targetTransactionPolicy, targetTransactionReport, currentUserLogin, targetTransaction]);
 
     const data = useMemo(() => {
         if (!eligibleTransactions) {
@@ -114,8 +115,11 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr
         }
 
         const reports = targetTransactionReport && sourceTransactionReport ? [targetTransactionReport, sourceTransactionReport] : undefined;
-        setupMergeTransactionDataAndNavigate(transactionID, [targetTransaction, sourceTransaction], localeCompare, reports, true);
-    }, [transactionID, targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport, localeCompare]);
+        setupMergeTransactionDataAndNavigate(transactionID, [targetTransaction, sourceTransaction], localeCompare, reports, true, undefined, [
+            targetTransactionPolicy,
+            sourceTransactionPolicy,
+        ]);
+    }, [transactionID, targetTransaction, sourceTransaction, targetTransactionReport, sourceTransactionReport, localeCompare, targetTransactionPolicy, sourceTransactionPolicy]);
 
     const confirmButtonOptions = {
         showButton: true,
diff --git a/src/pages/TransactionMerge/ReceiptReviewPage.tsx b/src/pages/TransactionMerge/ReceiptReviewPage.tsx
index 644d9109b1f8..8c523b894fcb 100644
--- a/src/pages/TransactionMerge/ReceiptReviewPage.tsx
+++ b/src/pages/TransactionMerge/ReceiptReviewPage.tsx
@@ -34,7 +34,7 @@ function ReceiptReviewPage({route}: ReceiptReviewPageProps) {
     const {transactionID, isOnSearch, backTo} = route.params;
 
     const [mergeTransaction, mergeTransactionMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.MERGE_TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true});
-    const {targetTransaction, sourceTransaction} = useMergeTransactions({mergeTransaction});
+    const {targetTransaction, sourceTransaction, targetTransactionPolicy, sourceTransactionPolicy} = useMergeTransactions({mergeTransaction});
 
     const transactions = [targetTransaction, sourceTransaction].filter((transaction): transaction is Transaction => !!transaction);
 
@@ -47,7 +47,7 @@ function ReceiptReviewPage({route}: ReceiptReviewPageProps) {
             return;
         }
 
-        const {conflictFields, mergeableData} = getMergeableDataAndConflictFields(targetTransaction, sourceTransaction, localeCompare);
+        const {conflictFields, mergeableData} = getMergeableDataAndConflictFields(targetTransaction, sourceTransaction, localeCompare, [], targetTransactionPolicy, sourceTransactionPolicy);
         if (!conflictFields.length) {
             // If there are no conflict fields, we should set mergeable data and navigate to the confirmation page
             setMergeTransactionKey(transactionID, mergeableData);
diff --git a/src/types/onyx/MergeTransaction.ts b/src/types/onyx/MergeTransaction.ts
index 15dcc28b4835..22bcb91ed05b 100644
--- a/src/types/onyx/MergeTransaction.ts
+++ b/src/types/onyx/MergeTransaction.ts
@@ -82,6 +82,9 @@ type MergeTransaction = {
 
     /** Tax code of the transaction */
     taxCode: string;
+
+    /** Tax name to display in merge transaction flow */
+    taxName: string;
 };
 
 export default MergeTransaction;
diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts
index aa97dbbde5fe..b65368de314f 100644
--- a/src/types/onyx/Transaction.ts
+++ b/src/types/onyx/Transaction.ts
@@ -445,6 +445,9 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback<
         /** The transaction tax value */
         taxValue?: string | undefined;
 
+        /** The transaction tax name */
+        taxName?: string;
+
         /** Whether the expense is billable */
         billable?: boolean;
 
diff --git a/tests/utils/collections/mergeTransaction.ts b/tests/utils/collections/mergeTransaction.ts
index 7e47dc025816..1ba99c8bae13 100644
--- a/tests/utils/collections/mergeTransaction.ts
+++ b/tests/utils/collections/mergeTransaction.ts
@@ -27,5 +27,6 @@ export default function createRandomMergeTransaction(index: number): MergeTransa
         taxAmount: randAmount(),
         taxValue: randAmount().toString(),
         taxCode: randWord(),
+        taxName: randWord(),
     };
 }

From 02fbb148ef1415aead7417cd2b02b1ad7425e755 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 16 Jan 2026 01:33:45 +0700
Subject: [PATCH 46/54] fix lint

---
 src/hooks/useSelectedTransactionsActions.ts      | 5 ++---
 src/libs/DebugUtils.ts                           | 1 +
 src/libs/actions/MergeTransaction.ts             | 2 +-
 src/pages/TransactionMerge/DetailsReviewPage.tsx | 7 +++----
 tests/unit/MergeTransactionUtilsTest.ts          | 6 ------
 5 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts
index c0b61bf1a4ab..b0e286a1bf1e 100644
--- a/src/hooks/useSelectedTransactionsActions.ts
+++ b/src/hooks/useSelectedTransactionsActions.ts
@@ -318,7 +318,7 @@ function useSelectedTransactionsActions({
                             [],
                             false,
                             isOnSearch,
-                            Array(selectedTransactionsList.length).fill(policy),
+                            selectedTransactionsList.length > 1 ? [policy, policy] : undefined,
                         ),
                 });
             }
@@ -376,7 +376,7 @@ function useSelectedTransactionsActions({
         }
         return options;
     }, [
-        session?.email,
+        session,
         selectedTransactionIDs,
         report,
         selectedTransactionsList,
@@ -398,7 +398,6 @@ function useSelectedTransactionsActions({
         lastVisitedPath,
         allTransactions,
         allReports,
-        session?.accountID,
         showDeleteModal,
         allTransactionViolations,
         expensifyIcons.Stopwatch,
diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts
index 38f25b3ec385..cd1c064a6186 100644
--- a/src/libs/DebugUtils.ts
+++ b/src/libs/DebugUtils.ts
@@ -951,6 +951,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
         case 'category':
         case 'merchant':
         case 'taxCode':
+        case 'taxName':
         case 'modifiedCurrency':
         case 'modifiedMerchant':
         case 'transactionID':
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 5f64ff125659..cda3a8fc43ff 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -47,7 +47,7 @@ function setupMergeTransactionDataAndNavigate(
     searchReports?: Report[],
     isSelectingSourceTransaction?: boolean,
     isOnSearch?: boolean,
-    policies?: OnyxEntry<Policy>[],
+    policies?: Array<OnyxEntry<Policy>>,
 ) {
     if (!transactions.length || transactions.length > 2) {
         return;
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index b4c272136eed..a228a48c5c4b 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -49,11 +49,10 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
     });
 
     const [hasErrors, setHasErrors] = useState<Partial<Record<MergeFieldKey, boolean>>>({});
-    const [conflictFields, setConflictFields] = useState<MergeFieldKey[]>([]);
 
-    useEffect(() => {
+    const conflictFields = useMemo(() => {
         if (!transactionID || !targetTransaction || !sourceTransaction) {
-            return;
+            return [];
         }
 
         const {conflictFields: detectedConflictFields, mergeableData} = getMergeableDataAndConflictFields(
@@ -66,7 +65,7 @@ function DetailsReviewPage({route}: DetailsReviewPageProps) {
         );
 
         setMergeTransactionKey(transactionID, mergeableData);
-        setConflictFields(detectedConflictFields as MergeFieldKey[]);
+        return detectedConflictFields as MergeFieldKey[];
     }, [targetTransaction, sourceTransaction, transactionID, localeCompare, sourceTransactionReport, targetTransactionReport, targetTransactionPolicy, sourceTransactionPolicy]);
 
     // Handle selection
diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index f2d3657d2872..6ce505283fcf 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -348,12 +348,6 @@ describe('MergeTransactionUtils', () => {
                 tag: 'Same Tag',
                 billable: false,
                 attendees: [],
-                selectedTransactionByField: {
-                    merchant: targetTransaction.transactionID,
-                    category: targetTransaction.transactionID,
-                    tag: sourceTransaction.transactionID,
-                    billable: targetTransaction.transactionID,
-                },
             });
         });
 

From 10ed005a4ba5c4ca35507b1a47a36b14e5ef813d Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 16 Jan 2026 08:43:50 +0700
Subject: [PATCH 47/54] fix lint

---
 src/hooks/useSelectedTransactionsActions.ts             | 4 +++-
 src/pages/TransactionMerge/DetailsReviewPage.tsx        | 2 +-
 tests/unit/hooks/useSelectedTransactionsActions.test.ts | 2 +-
 3 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts
index b0e286a1bf1e..5f6a8aa76d27 100644
--- a/src/hooks/useSelectedTransactionsActions.ts
+++ b/src/hooks/useSelectedTransactionsActions.ts
@@ -165,6 +165,7 @@ function useSelectedTransactionsActions({
         setIsDeleteModalVisible(false);
     }, []);
 
+    // eslint-disable-next-line react-hooks/preserve-manual-memoization
     const computedOptions = useMemo(() => {
         if (!selectedTransactionIDs.length) {
             return [];
@@ -376,7 +377,8 @@ function useSelectedTransactionsActions({
         }
         return options;
     }, [
-        session,
+        session?.email,
+        session?.accountID,
         selectedTransactionIDs,
         report,
         selectedTransactionsList,
diff --git a/src/pages/TransactionMerge/DetailsReviewPage.tsx b/src/pages/TransactionMerge/DetailsReviewPage.tsx
index a228a48c5c4b..d7575790f851 100644
--- a/src/pages/TransactionMerge/DetailsReviewPage.tsx
+++ b/src/pages/TransactionMerge/DetailsReviewPage.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useEffect, useMemo, useState} from 'react';
+import React, {useCallback, useMemo, useState} from 'react';
 import {View} from 'react-native';
 import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
 import Button from '@components/Button';
diff --git a/tests/unit/hooks/useSelectedTransactionsActions.test.ts b/tests/unit/hooks/useSelectedTransactionsActions.test.ts
index f5043ae58930..ab8dd3a440cf 100644
--- a/tests/unit/hooks/useSelectedTransactionsActions.test.ts
+++ b/tests/unit/hooks/useSelectedTransactionsActions.test.ts
@@ -655,6 +655,6 @@ describe('useSelectedTransactionsActions', () => {
 
         mergeOption?.onSelected?.();
 
-        expect(setupMergeTransactionDataAndNavigate).toHaveBeenCalledWith(transaction.transactionID, [transaction], mockLocalCompare, [], false, false);
+        expect(setupMergeTransactionDataAndNavigate).toHaveBeenCalledWith(transaction.transactionID, [transaction], mockLocalCompare, [], false, false, undefined);
     });
 });

From 0e348f1ee2e4f5347399f2b0752678d8c4622bc5 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 16 Jan 2026 16:00:34 +0700
Subject: [PATCH 48/54] remove lint silent

---
 src/hooks/useSelectedTransactionsActions.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts
index 5f6a8aa76d27..66b427b701ea 100644
--- a/src/hooks/useSelectedTransactionsActions.ts
+++ b/src/hooks/useSelectedTransactionsActions.ts
@@ -165,7 +165,6 @@ function useSelectedTransactionsActions({
         setIsDeleteModalVisible(false);
     }, []);
 
-    // eslint-disable-next-line react-hooks/preserve-manual-memoization
     const computedOptions = useMemo(() => {
         if (!selectedTransactionIDs.length) {
             return [];

From 4f45a0b7fe8e6c7fb63ba68532b31086965e1fd4 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 27 Jan 2026 01:37:59 +0700
Subject: [PATCH 49/54] fix: tax does not show in confirmation step

---
 src/libs/MergeTransactionUtils.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index eac6251c8c9c..c2d3fc80f060 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -383,6 +383,7 @@ function buildMergedTransactionData(targetTransaction: OnyxEntry<Transaction>, m
         taxValue: mergeTransaction.taxValue,
         taxAmount: mergeTransaction.taxAmount,
         taxCode: mergeTransaction.taxCode,
+        taxName: mergeTransaction.taxName,
     };
 }
 

From 07a2c7c66731e630fddb1edee5c9f850b0bea454 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Tue, 27 Jan 2026 02:20:41 +0700
Subject: [PATCH 50/54] fix unit test

---
 tests/unit/MergeTransactionUtilsTest.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index 6ce505283fcf..5240b6be1d7c 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -601,6 +601,7 @@ describe('MergeTransactionUtils', () => {
                 taxValue: '9%',
                 taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, 'USD')),
                 taxCode: 'id_TAX_RATE_1',
+                taxName: 'Tax Rate 1 (9%)',
             };
 
             const result = buildMergedTransactionData(targetTransaction, mergeTransaction);
@@ -631,6 +632,7 @@ describe('MergeTransactionUtils', () => {
                 taxValue: '9%',
                 taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, 'USD')),
                 taxCode: 'id_TAX_RATE_1',
+                taxName: 'Tax Rate 1 (9%)',
             });
         });
     });

From 6a7da5ec5ed3d802d3736ed787435922a6e5308e Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Fri, 30 Jan 2026 02:32:12 +0700
Subject: [PATCH 51/54] fix: get correct report and policy for unreported
 expenses

---
 src/hooks/useMergeTransactions.ts | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/src/hooks/useMergeTransactions.ts b/src/hooks/useMergeTransactions.ts
index 262fc8cc81c5..619132aae3c0 100644
--- a/src/hooks/useMergeTransactions.ts
+++ b/src/hooks/useMergeTransactions.ts
@@ -1,10 +1,12 @@
 import type {OnyxEntry} from 'react-native-onyx';
 import {useSearchContext} from '@components/Search/SearchContext';
 import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
-import {getTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils';
+import {getReportIDForExpense, getTransactionFromMergeTransaction} from '@libs/MergeTransactionUtils';
+import {isExpenseUnreported} from '@libs/TransactionUtils';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {MergeTransaction, Policy, Report, SearchResults, Transaction} from '@src/types/onyx';
 import useOnyx from './useOnyx';
+import usePolicyForMovingExpenses from './usePolicyForMovingExpenses';
 
 type UseMergeTransactionsProps = {
     mergeTransaction?: MergeTransaction;
@@ -39,6 +41,7 @@ function getTransaction(
 
 function useMergeTransactions({mergeTransaction}: UseMergeTransactionsProps): UseMergeTransactionsReturn {
     const {currentSearchHash, currentSearchResults} = useSearchContext();
+    const {policyForMovingExpensesID} = usePolicyForMovingExpenses();
 
     const [onyxTargetTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(mergeTransaction?.targetTransactionID)}`, {
         canBeMissing: true,
@@ -50,26 +53,31 @@ function useMergeTransactions({mergeTransaction}: UseMergeTransactionsProps): Us
     const targetTransaction = getTransaction(mergeTransaction, mergeTransaction?.targetTransactionID, onyxTargetTransaction, currentSearchResults);
     const sourceTransaction = getTransaction(mergeTransaction, mergeTransaction?.sourceTransactionID, onyxSourceTransaction, currentSearchResults);
 
-    let [targetTransactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(targetTransaction?.reportID)}`, {
+    const targetTransactionReportID = getReportIDForExpense(targetTransaction);
+    const sourceTransactionReportID = getReportIDForExpense(sourceTransaction);
+    let [targetTransactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(targetTransactionReportID)}`, {
         canBeMissing: true,
     });
-    let [sourceTransactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceTransaction?.reportID)}`, {
+    let [sourceTransactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceTransactionReportID)}`, {
         canBeMissing: true,
     });
-    let [targetTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(targetTransactionReport?.policyID)}`, {
+
+    const targetTransactionPolicyID = isExpenseUnreported(targetTransaction) ? policyForMovingExpensesID : targetTransactionReport?.policyID;
+    const sourceTransactionPolicyID = isExpenseUnreported(sourceTransaction) ? policyForMovingExpensesID : sourceTransactionReport?.policyID;
+    let [targetTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(targetTransactionPolicyID)}`, {
         canBeMissing: true,
     });
-    let [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(sourceTransactionReport?.policyID)}`, {
+    let [sourceTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(sourceTransactionPolicyID)}`, {
         canBeMissing: true,
     });
 
     if (currentSearchHash && currentSearchResults?.data) {
         // If we're on search and main collection reports are not available, get them from the search snapshot
-        targetTransactionReport = targetTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${targetTransaction?.reportID}`];
-        sourceTransactionReport = sourceTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransaction?.reportID}`];
+        targetTransactionReport = targetTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionReportID}`];
+        sourceTransactionReport = sourceTransactionReport ?? currentSearchResults?.data[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransactionReportID}`];
         // If we're on search, search snapshot policies are more up to date
-        targetTransactionPolicy = currentSearchResults?.data[`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionReport?.policyID}`];
-        sourceTransactionPolicy = currentSearchResults?.data[`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionReport?.policyID}`];
+        targetTransactionPolicy = currentSearchResults?.data[`${ONYXKEYS.COLLECTION.POLICY}${targetTransactionPolicyID}`];
+        sourceTransactionPolicy = currentSearchResults?.data[`${ONYXKEYS.COLLECTION.POLICY}${sourceTransactionPolicyID}`];
     }
 
     return {

From f319cd27e23fb7f6d99cd47b277e33e6ea822ecd Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 9 Feb 2026 21:20:02 +0700
Subject: [PATCH 52/54] pass taxPolicyID param

---
 src/libs/MergeTransactionUtils.ts    | 1 +
 src/libs/actions/MergeTransaction.ts | 1 +
 src/types/onyx/MergeTransaction.ts   | 3 +++
 3 files changed, 5 insertions(+)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index ce0f9ccdaed7..29dcd80dfbc0 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -627,6 +627,7 @@ function getMergeFieldUpdatedValues<K extends MergeFieldKey>({
     if (field === 'taxValue') {
         updatedValues.taxCode = transaction?.taxCode;
         updatedValues.taxName = getTaxName(policy, transaction) ?? transaction?.taxValue ?? '';
+        updatedValues.taxPolicyID = policy?.id;
         if (mergeTransaction?.amount) {
             updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(transaction?.taxValue, mergeTransaction.amount, mergeTransaction.currency));
         }
diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts
index 2e0c2a7194c3..e8f67b724326 100644
--- a/src/libs/actions/MergeTransaction.ts
+++ b/src/libs/actions/MergeTransaction.ts
@@ -355,6 +355,7 @@ function mergeTransactionRequest({
         reimbursable: mergeTransaction.reimbursable,
         tag: mergeTransaction.tag,
         taxCode: mergeTransaction.taxCode,
+        taxPolicyID: mergeTransaction.taxPolicyID,
         receiptID: mergeTransaction.receipt?.receiptID,
         reportID: mergeTransaction.reportID,
     };
diff --git a/src/types/onyx/MergeTransaction.ts b/src/types/onyx/MergeTransaction.ts
index 22bcb91ed05b..08be4883f296 100644
--- a/src/types/onyx/MergeTransaction.ts
+++ b/src/types/onyx/MergeTransaction.ts
@@ -85,6 +85,9 @@ type MergeTransaction = {
 
     /** Tax name to display in merge transaction flow */
     taxName: string;
+
+    /** Policy ID of the selected tax rate for the transaction */
+    taxPolicyID: string;
 };
 
 export default MergeTransaction;

From 48f006c895a4845835e5506c04547572bd29a6f2 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Mon, 9 Feb 2026 21:29:53 +0700
Subject: [PATCH 53/54] fix typescript check

---
 tests/utils/collections/mergeTransaction.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/utils/collections/mergeTransaction.ts b/tests/utils/collections/mergeTransaction.ts
index 1ba99c8bae13..e7f1c0bd8d33 100644
--- a/tests/utils/collections/mergeTransaction.ts
+++ b/tests/utils/collections/mergeTransaction.ts
@@ -28,5 +28,6 @@ export default function createRandomMergeTransaction(index: number): MergeTransa
         taxValue: randAmount().toString(),
         taxCode: randWord(),
         taxName: randWord(),
+        taxPolicyID: randWord(),
     };
 }

From 064603da228a8cf6430927ee7e0e316cd3d0fe14 Mon Sep 17 00:00:00 2001
From: dominictb <tb-dominic@outlook.com>
Date: Wed, 18 Feb 2026 01:16:48 +0700
Subject: [PATCH 54/54] fix typescript

---
 src/libs/MergeTransactionUtils.ts       | 6 +++---
 tests/unit/MergeTransactionUtilsTest.ts | 6 +++---
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/libs/MergeTransactionUtils.ts b/src/libs/MergeTransactionUtils.ts
index 29dcd80dfbc0..372d7429fa0f 100644
--- a/src/libs/MergeTransactionUtils.ts
+++ b/src/libs/MergeTransactionUtils.ts
@@ -8,7 +8,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import type {MergeTransaction, Policy, Report, SearchResults, Transaction} from '@src/types/onyx';
 import type {Attendee} from '@src/types/onyx/IOU';
 import SafeString from '@src/utils/SafeString';
-import {convertToBackendAmount, convertToDisplayString} from './CurrencyUtils';
+import {convertToBackendAmount, convertToDisplayString, getCurrencyDecimals} from './CurrencyUtils';
 import Parser from './Parser';
 import {getCommaSeparatedTagNameWithSanitizedColons} from './PolicyUtils';
 import {getIOUActionForReportID} from './ReportActionsUtils';
@@ -603,7 +603,7 @@ function getMergeFieldUpdatedValues<K extends MergeFieldKey>({
     if (field === 'amount') {
         updatedValues.currency = getCurrency(transaction);
         if (mergeTransaction?.taxValue && transaction?.amount) {
-            updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(mergeTransaction?.taxValue, transaction.amount, getCurrency(transaction)));
+            updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(mergeTransaction?.taxValue, transaction.amount, getCurrencyDecimals(getCurrency(transaction))));
         }
     }
 
@@ -629,7 +629,7 @@ function getMergeFieldUpdatedValues<K extends MergeFieldKey>({
         updatedValues.taxName = getTaxName(policy, transaction) ?? transaction?.taxValue ?? '';
         updatedValues.taxPolicyID = policy?.id;
         if (mergeTransaction?.amount) {
-            updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(transaction?.taxValue, mergeTransaction.amount, mergeTransaction.currency));
+            updatedValues.taxAmount = convertToBackendAmount(calculateTaxAmount(transaction?.taxValue, mergeTransaction.amount, getCurrencyDecimals(getCurrency(transaction))));
         }
     }
 
diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts
index c43ed9ce43e7..4fe745b772a2 100644
--- a/tests/unit/MergeTransactionUtilsTest.ts
+++ b/tests/unit/MergeTransactionUtilsTest.ts
@@ -1,5 +1,5 @@
 import Onyx from 'react-native-onyx';
-import {convertToBackendAmount} from '@libs/CurrencyUtils';
+import {convertToBackendAmount, getCurrencyDecimals} from '@libs/CurrencyUtils';
 import {
     areTransactionsEligibleForMerge,
     buildMergedTransactionData,
@@ -600,7 +600,7 @@ describe('MergeTransactionUtils', () => {
                 waypoints: {waypoint0: {name: 'Selected waypoint'}},
                 customUnit: {name: CONST.CUSTOM_UNITS.NAME_DISTANCE, customUnitID: 'distance1', quantity: 100},
                 taxValue: '9%',
-                taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, 'USD')),
+                taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, getCurrencyDecimals(CONST.CURRENCY.USD))),
                 taxCode: 'id_TAX_RATE_1',
                 taxName: 'Tax Rate 1 (9%)',
             };
@@ -631,7 +631,7 @@ describe('MergeTransactionUtils', () => {
                 reportID: '1',
                 reportName: 'Test Report',
                 taxValue: '9%',
-                taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, 'USD')),
+                taxAmount: convertToBackendAmount(calculateTaxAmount('9%', 2000, getCurrencyDecimals(CONST.CURRENCY.USD))),
                 taxCode: 'id_TAX_RATE_1',
                 taxName: 'Tax Rate 1 (9%)',
             });
