From b7161465a02d6ee073df7d48b69ae29ceae8b66f Mon Sep 17 00:00:00 2001 From: "dmkt9 (via MelvinBot)" Date: Thu, 16 Apr 2026 14:27:46 +0000 Subject: [PATCH 1/4] Fix duplicate tax handling to preserve original tax data when unchanged When resolving duplicates, stop sending taxCode/taxAmount/taxValue if the tax code hasn't changed or was deleted. This prevents overwriting existing tax data with empty values, which caused empty tax rate rows and incorrect tax amounts after confirming duplicate resolution. Co-authored-by: dmkt9 --- src/libs/API/parameters/MergeDuplicatesParams.ts | 2 +- src/libs/TransactionUtils/index.ts | 5 ++--- src/libs/actions/IOU/Duplicate.ts | 16 ++++++++-------- src/pages/TransactionDuplicate/Confirmation.tsx | 12 +++++++++--- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/libs/API/parameters/MergeDuplicatesParams.ts b/src/libs/API/parameters/MergeDuplicatesParams.ts index 7f2c3daa7eaf..8edbfa0e8cac 100644 --- a/src/libs/API/parameters/MergeDuplicatesParams.ts +++ b/src/libs/API/parameters/MergeDuplicatesParams.ts @@ -10,7 +10,7 @@ type MergeDuplicatesParams = { billable: boolean; reimbursable: boolean; tag: string; - taxCode: string; + taxCode?: string; receiptID: number; reportID: string | undefined; reportActionID?: string | undefined; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 6a994ee91cba..b071ea538af2 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -2559,8 +2559,8 @@ function buildNewTransactionAfterReviewingDuplicates(reviewDuplicateTransaction: modifiedMerchant: reviewDuplicateTransaction?.merchant, merchant: reviewDuplicateTransaction?.merchant, comment: {...reviewDuplicateTransaction?.comment, comment: reviewDuplicateTransaction?.description}, - // Clear stale taxName/taxValue so MoneyRequestView derives them fresh from the policy using the updated taxCode - ...(reviewDuplicateTransaction?.taxCode !== undefined && {taxName: undefined, taxValue: undefined}), + // If the taxCode changes, clear stale taxName/taxValue so MoneyRequestView derives them fresh from the policy using the updated taxCode + ...(reviewDuplicateTransaction?.taxCode !== undefined && reviewDuplicateTransaction?.taxCode !== duplicatedTransaction?.taxCode && {taxName: undefined, taxValue: undefined}), }; } @@ -2581,7 +2581,6 @@ function buildMergeDuplicatesParams( reimbursable: reviewDuplicates?.reimbursable ?? false, category: reviewDuplicates?.category ?? '', tag: reviewDuplicates?.tag ?? '', - taxCode: reviewDuplicates?.taxCode ?? '', merchant: reviewDuplicates?.merchant ?? '', comment: reviewDuplicates?.description ?? '', }; diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index c263b6032277..fda4aedeedbf 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -75,7 +75,7 @@ function getIOUActionForTransactions(transactionIDList: Array { const taxCode = reviewDuplicates?.taxCode ?? ''; const taxRate = taxCode ? policy?.taxRates?.taxes?.[taxCode] : undefined; + // Preserve taxAmount and taxValue if taxCode is deleted or remains unchanged compared to duplicatedTransaction?.taxCode. + if (!taxRate || (taxCode && duplicatedTransaction?.taxCode === taxCode) || reviewDuplicates?.taxAmount === undefined) { + return; + } + return { - taxAmount: -(reviewDuplicates?.taxAmount ?? 0), - taxValue: taxRate?.value ?? '', + taxAmount: -reviewDuplicates.taxAmount, + taxValue: taxRate?.value, + taxCode, }; - }, [reviewDuplicates?.taxCode, reviewDuplicates?.taxAmount, policy?.taxRates?.taxes]); + }, [reviewDuplicates?.taxCode, reviewDuplicates?.taxAmount, policy?.taxRates?.taxes, duplicatedTransaction?.taxCode]); const isReportOwner = iouReport?.ownerAccountID === currentUserPersonalDetails?.accountID; const handleMergeDuplicates = useCallback(() => { From c2e683f1c75e4a6178b3f2ec3b832371248680f4 Mon Sep 17 00:00:00 2001 From: "dmkt9 (via MelvinBot)" Date: Thu, 16 Apr 2026 15:07:47 +0000 Subject: [PATCH 2/4] Preserve taxAmount when tax code is unchanged during duplicate resolution Co-authored-by: dmkt9 --- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index 96dd7a8405e3..492c447bfd13 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -64,6 +64,10 @@ function ReviewTaxRate() { ); const getTaxAmount = useCallback( (taxID: string) => { + // If the tax code remains unchanged, preserve the tax amount to avoid resetting it to the default value when resolving duplicates. + if (taxID === transaction?.taxCode) { + return; + } const taxPercentage = getTaxValue(policy, transaction, taxID); const decimals = getCurrencyDecimals(transaction?.currency); return convertToBackendAmount(calculateTaxAmount(taxPercentage ?? '', getAmount(transaction), decimals)); From fed3eb74cd1e2674922893c38a2ba23c196fed83 Mon Sep 17 00:00:00 2001 From: "dmkt9 (via MelvinBot)" Date: Thu, 16 Apr 2026 15:51:25 +0000 Subject: [PATCH 3/4] Compare tax code against duplicatedTransaction instead of current transaction Co-authored-by: dmkt9 --- src/pages/TransactionDuplicate/ReviewTaxCode.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index 492c447bfd13..bcca68cbd62c 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -31,6 +31,7 @@ function ReviewTaxRate() { const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const transactionID = getTransactionID(transactionThreadReport); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`); + const [duplicatedTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(reviewDuplicates?.transactionID)}`); const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const allDuplicateIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], @@ -65,14 +66,14 @@ function ReviewTaxRate() { const getTaxAmount = useCallback( (taxID: string) => { // If the tax code remains unchanged, preserve the tax amount to avoid resetting it to the default value when resolving duplicates. - if (taxID === transaction?.taxCode) { + if (taxID === duplicatedTransaction?.taxCode) { return; } const taxPercentage = getTaxValue(policy, transaction, taxID); const decimals = getCurrencyDecimals(transaction?.currency); return convertToBackendAmount(calculateTaxAmount(taxPercentage ?? '', getAmount(transaction), decimals)); }, - [policy, transaction, getCurrencyDecimals], + [policy, transaction, getCurrencyDecimals, duplicatedTransaction?.taxCode], ); const setTaxCode = useCallback( From 47d2c2748f3ae58c590f2d24ad4123718d006fc7 Mon Sep 17 00:00:00 2001 From: "dmkt9 (via MelvinBot)" Date: Thu, 16 Apr 2026 16:23:08 +0000 Subject: [PATCH 4/4] Use duplicated transaction's policy for tax rate lookup in Confirmation Co-authored-by: dmkt9 --- src/pages/TransactionDuplicate/Confirmation.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 0851e7c29f30..40dd511a89bc 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -57,6 +57,7 @@ function Confirmation() { const [reviewDuplicatesReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reviewDuplicates?.reportID}`); const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(reviewDuplicatesReport?.policyID)}`); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`); + const [duplicatedTransactionPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(reviewDuplicatesReport?.policyID)}`); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(reviewDuplicatesReport?.policyID)}`); const compareResult = TransactionUtils.compareDuplicateTransactionFields(policyTags ?? {}, transaction, allDuplicates, reviewDuplicatesReport, undefined, policy, policyCategories); const {goBack} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'confirmation', route.params.threadReportID, route.params.backTo); @@ -75,7 +76,7 @@ function Confirmation() { ); const taxData = useMemo(() => { const taxCode = reviewDuplicates?.taxCode ?? ''; - const taxRate = taxCode ? policy?.taxRates?.taxes?.[taxCode] : undefined; + const taxRate = taxCode ? duplicatedTransactionPolicy?.taxRates?.taxes?.[taxCode] : undefined; // Preserve taxAmount and taxValue if taxCode is deleted or remains unchanged compared to duplicatedTransaction?.taxCode. if (!taxRate || (taxCode && duplicatedTransaction?.taxCode === taxCode) || reviewDuplicates?.taxAmount === undefined) { return; @@ -86,7 +87,7 @@ function Confirmation() { taxValue: taxRate?.value, taxCode, }; - }, [reviewDuplicates?.taxCode, reviewDuplicates?.taxAmount, policy?.taxRates?.taxes, duplicatedTransaction?.taxCode]); + }, [reviewDuplicates?.taxCode, reviewDuplicates?.taxAmount, duplicatedTransactionPolicy?.taxRates?.taxes, duplicatedTransaction?.taxCode]); const isReportOwner = iouReport?.ownerAccountID === currentUserPersonalDetails?.accountID; const handleMergeDuplicates = useCallback(() => {