From 4aca2dfade0a75842a114af3e3840ea2b9f04c94 Mon Sep 17 00:00:00 2001 From: "Youssef Lourayad (via MelvinBot)" Date: Fri, 10 Apr 2026 12:04:40 +0000 Subject: [PATCH 1/3] Hide tax fields for time and per diem expenses in MoneyRequestView Time and per diem expenses should never show tax fields. After the tax-disabled-alert feature was added, time expenses with a taxCode would incorrectly show tax fields because isTaxTrackingEnabled returns false for time requests, triggering shouldShowTaxDisabledAlert. Add guard to shouldShowTax to exclude time and per diem requests. Co-authored-by: Youssef Lourayad --- .../ReportActionItem/MoneyRequestView.tsx | 2 +- tests/ui/MoneyRequestViewTest.tsx | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 3a8bffc0124c..47bc2d963951 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -453,7 +453,7 @@ function MoneyRequestView({ const isTaxEnabled = isTaxTrackingEnabled(isPolicyExpenseChat || isExpenseUnreported, policy, isDistanceRequest, isPerDiemRequest, isTimeRequest); const shouldShowTaxDisabledAlert = !isTaxEnabled && !!transaction?.taxCode; - const shouldShowTax = isTaxEnabled || !!transaction?.taxName || shouldShowTaxDisabledAlert; + const shouldShowTax = (isTaxEnabled || !!transaction?.taxName || shouldShowTaxDisabledAlert) && !isTimeRequest && !isPerDiemRequest; let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; diff --git a/tests/ui/MoneyRequestViewTest.tsx b/tests/ui/MoneyRequestViewTest.tsx index 3b525b2b27c6..74c06d8cb8e7 100644 --- a/tests/ui/MoneyRequestViewTest.tsx +++ b/tests/ui/MoneyRequestViewTest.tsx @@ -230,6 +230,34 @@ describe('MoneyRequestView edit fields', () => { }); }); + it('should not show tax fields for time expenses even when transaction has tax data', async () => { + const threadReport = { + ...LHNTestUtils.getFakeReport(), + parentReportID: expenseReportID, + parentReportActionID, + }; + + await setupTestData(); + + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + taxCode: 'TAX_10', + taxAmount: 500, + taxValue: '10%', + comment: {type: CONST.TRANSACTION.TYPE.TIME}, + }); + }); + await waitForBatchedUpdatesWithAct(); + + renderMoneyRequestView(threadReport, {tax: {trackingEnabled: true}}); + await waitForBatchedUpdatesWithAct(); + + await waitFor(() => { + expect(screen.queryByTestId('menu-item-common.tax')).not.toBeOnTheScreen(); + expect(screen.queryByTestId('menu-item-iou.taxAmount')).not.toBeOnTheScreen(); + }); + }); + it('should show amount and merchant as readonly when report is settled', async () => { const threadReport = { ...LHNTestUtils.getFakeReport(), From 06fddc4b0e24a5f264d1f82800afba66d839f330 Mon Sep 17 00:00:00 2001 From: "Youssef Lourayad (via MelvinBot)" Date: Fri, 10 Apr 2026 18:18:44 +0000 Subject: [PATCH 2/3] Move time/perDiem guard from shouldShowTax to shouldShowTaxDisabledAlert Since isTaxTrackingEnabled already returns false for time and per diem requests, the guard only needs to be on shouldShowTaxDisabledAlert to prevent the "Tax no longer valid" violation from appearing. Co-authored-by: Youssef Lourayad --- src/components/ReportActionItem/MoneyRequestView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 47bc2d963951..9516387f0b94 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -452,8 +452,8 @@ function MoneyRequestView({ const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => (pendingAction ? undefined : transaction?.pendingFields?.[fieldPath]); const isTaxEnabled = isTaxTrackingEnabled(isPolicyExpenseChat || isExpenseUnreported, policy, isDistanceRequest, isPerDiemRequest, isTimeRequest); - const shouldShowTaxDisabledAlert = !isTaxEnabled && !!transaction?.taxCode; - const shouldShowTax = (isTaxEnabled || !!transaction?.taxName || shouldShowTaxDisabledAlert) && !isTimeRequest && !isPerDiemRequest; + const shouldShowTaxDisabledAlert = !isTaxEnabled && !!transaction?.taxCode && !isTimeRequest && !isPerDiemRequest; + const shouldShowTax = isTaxEnabled || !!transaction?.taxName || shouldShowTaxDisabledAlert; let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; From 5aa0800120c21d067c9f4250d6eb591d55469355 Mon Sep 17 00:00:00 2001 From: "Youssef Lourayad (via MelvinBot)" Date: Sat, 11 Apr 2026 17:06:44 +0000 Subject: [PATCH 3/3] Skip TAX_OUT_OF_POLICY violation for time and per diem expenses Time and per diem expenses should never trigger the taxOutOfPolicy violation. The violation was being incorrectly generated because isTaxTrackingEnabled returns false for these expense types, causing the fallback hasTransactionTaxData check to fire when the transaction had a taxCode set from a tax-enabled workspace. Co-authored-by: Youssef Lourayad --- src/libs/Violations/ViolationsUtils.ts | 2 +- tests/unit/ViolationUtilsTest.ts | 27 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts index 3061b2fefd5c..f79885ff98e3 100644 --- a/src/libs/Violations/ViolationsUtils.ts +++ b/src/libs/Violations/ViolationsUtils.ts @@ -663,7 +663,7 @@ const ViolationsUtils = { } const hasTransactionTaxData = !!updatedTransaction.taxCode || !!updatedTransaction.taxValue || !!updatedTransaction.taxAmount; - const shouldAddTaxOutOfPolicy = isPolicyTrackTaxEnabled ? !isTaxInPolicy : hasTransactionTaxData; + const shouldAddTaxOutOfPolicy = !isTimeRequest && !isPerDiemRequest && (isPolicyTrackTaxEnabled ? !isTaxInPolicy : hasTransactionTaxData); if (!hasTaxOutOfPolicyViolation && shouldAddTaxOutOfPolicy) { newTransactionViolations.push({name: CONST.VIOLATIONS.TAX_OUT_OF_POLICY, type: CONST.VIOLATION_TYPES.VIOLATION, showInReview: true}); diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 23943d8ee975..e970977283b9 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -1166,6 +1166,33 @@ describe('getViolationsOnyxData', () => { expect(result.value).not.toContainEqual(taxOutOfPolicyViolation); }); }); + + describe('time and per diem requests', () => { + it('should not add taxOutOfPolicy violation for time requests even with tax data and tax tracking disabled', () => { + policy.tax = {trackingEnabled: false}; + transaction.taxCode = 'SOME_TAX'; + transaction.comment = {...transaction.comment, type: CONST.TRANSACTION.TYPE.TIME}; + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policy, policyTags, policyCategories, false, false); + expect(result.value).not.toContainEqual(taxOutOfPolicyViolation); + }); + + it('should not add taxOutOfPolicy violation for per diem requests even with tax data and tax tracking disabled', () => { + policy.tax = {trackingEnabled: false}; + transaction.taxCode = 'SOME_TAX'; + transaction.comment = {...transaction.comment, type: CONST.TRANSACTION.TYPE.CUSTOM_UNIT, customUnit: {name: CONST.CUSTOM_UNITS.NAME_PER_DIEM_INTERNATIONAL}}; + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policy, policyTags, policyCategories, false, false); + expect(result.value).not.toContainEqual(taxOutOfPolicyViolation); + }); + + it('should not add taxOutOfPolicy violation for time requests even with invalid taxCode and tax tracking enabled', () => { + policy.tax = {trackingEnabled: true}; + transaction.taxCode = 'UNKNOWN_TAX'; + transaction.comment = {...transaction.comment, type: CONST.TRANSACTION.TYPE.TIME}; + policy.taxRates = {name: 'Taxes', defaultExternalID: 'TAX_10', defaultValue: '10%', foreignTaxDefault: 'TAX_10', taxes: {TAX_10: {name: '10%', value: '10%'}}}; + const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policy, policyTags, policyCategories, false, false); + expect(result.value).not.toContainEqual(taxOutOfPolicyViolation); + }); + }); }); describe('overTripLimit violation', () => {