Skip to content
20 changes: 17 additions & 3 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
setMoneyRequestPendingFields,
setMoneyRequestTag,
setMoneyRequestTaxAmount,
setMoneyRequestTaxRate,
setMoneyRequestTaxRateValues,
} from '@libs/actions/IOU';
import {computePerDiemExpenseAmount, isValidPerDiemExpenseAmount} from '@libs/actions/IOU/PerDiem';
import {adjustRemainingSplitShares, resetSplitShares, setIndividualShare, setSplitShares} from '@libs/actions/IOU/Split';
Expand Down Expand Up @@ -350,6 +350,7 @@ function MoneyRequestConfirmationList({
const isFromGlobalCreateAndCanEditParticipant = !!transaction?.isFromGlobalCreate && !isPerDiemRequest && !isTimeRequest;

const transactionID = transaction?.transactionID;
const previousTransactionCurrency = usePrevious(transaction?.currency);
const customUnitRateID = getRateID(transaction);

const subRates = transaction?.comment?.customUnit?.subRates ?? [];
Expand Down Expand Up @@ -380,14 +381,27 @@ function MoneyRequestConfirmationList({

// Update the tax code when the default changes (for example, because the transaction currency changed)
const defaultTaxCode = getDefaultTaxCode(policy, transaction) ?? (isMovingTransactionFromTrackExpense ? (getDefaultTaxCode(policyForMovingExpenses, transaction) ?? '') : '');
const defaultTaxValue = getTaxValue(policy, transaction, defaultTaxCode) ?? null;
const previousDefaultTaxCode = getDefaultTaxCode(policy, transaction, previousTransactionCurrency);
const shouldKeepCurrentTaxSelection = hasTaxRateWithMatchingValue(policy, transaction) && transaction?.taxCode !== previousDefaultTaxCode;

useEffect(() => {
if (!transactionID || isReadOnly || !shouldShowTax || isMovingTransactionFromTrackExpense) {
return;
}
setMoneyRequestTaxRate(transactionID, defaultTaxCode);

// Keep the user's current selection when it's still valid for the active policy.
if (shouldKeepCurrentTaxSelection) {
return;
}

setMoneyRequestTaxRateValues(transactionID, {
taxCode: defaultTaxCode,
taxValue: defaultTaxValue,
taxAmount: transaction?.taxAmount ?? null,
});
// trigger this useEffect also when policyID changes - the defaultTaxCode may stay the same
}, [defaultTaxCode, isMovingTransactionFromTrackExpense, isReadOnly, transactionID, policyID, shouldShowTax]);
}, [defaultTaxCode, defaultTaxValue, isMovingTransactionFromTrackExpense, isReadOnly, transactionID, policyID, shouldShowTax, shouldKeepCurrentTaxSelection, transaction?.taxAmount]);

const distance = getDistanceInMeters(transaction, unit);
const prevDistance = usePrevious(distance);
Expand Down
10 changes: 8 additions & 2 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,13 @@ function MoneyRequestView({
: convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), actualCurrency);

const taxRatesDescription = taxRates?.name;
const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction, isExpenseUnreported) : getTaxName(policy, transaction, isExpenseUnreported);

const baseTransaction = updatedTransaction ?? transaction;
const {taxCode, taxValue} = baseTransaction ?? {};

const taxRateTitle = getTaxName(policy, baseTransaction, isExpenseUnreported);
const selectedPolicyTaxValue = taxCode ? policy?.taxRates?.taxes?.[taxCode]?.value : undefined;
const hasTaxValueChanged = taxCode && taxValue !== undefined ? selectedPolicyTaxValue !== taxValue : false;

const actualTransactionDate = isFromMergeTransaction && updatedTransaction ? getFormattedCreated(updatedTransaction) : transactionDate;
const fallbackTaxRateTitle = transaction?.taxValue;
Expand Down Expand Up @@ -592,7 +598,7 @@ function MoneyRequestView({
const decodedCategoryName = getDecodedCategoryName(categoryValue);
const categoryCopyValue = !canEdit ? decodedCategoryName : undefined;
const cardCopyValue = cardProgramName;
const taxRateValue = transaction?.taxName ?? taxRateTitle ?? fallbackTaxRateTitle;
const taxRateValue = hasTaxValueChanged ? taxValue : (transaction?.taxName ?? taxRateTitle ?? fallbackTaxRateTitle ?? '');
const taxRateCopyValue = !canEditTaxFields ? taxRateValue : undefined;
const taxAmountTitle = formattedTaxAmount ? formattedTaxAmount.toString() : '';
const taxAmountCopyValue = !canEditTaxFields ? taxAmountTitle : undefined;
Expand Down
54 changes: 46 additions & 8 deletions src/components/TaxPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Navigation from '@libs/Navigation/Navigation';
import {getHeaderMessageForNonUserList} from '@libs/OptionsListUtils';
import {getTaxRatesSection} from '@libs/TaxOptionsListUtils';
import type {TaxRatesOption} from '@libs/TaxOptionsListUtils';
import {getEnabledTaxRateCount} from '@libs/TransactionUtils';
import {getDefaultTaxCode, getEnabledTaxRateCount, transformedTaxRates} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import type {IOUAction} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -26,7 +26,7 @@ type TaxPickerProps = {
transactionID?: string;

/** Callback to fire when a tax is pressed */
onSubmit: (tax: TaxRatesOption) => void;
onSubmit: (tax: TaxRatesOption, shouldClearTax?: boolean) => void;

/** The action to take */
action?: IOUAction;
Expand Down Expand Up @@ -68,10 +68,29 @@ function TaxPicker({selectedTaxRate = '', policyID, transactionID, onSubmit, act

const shouldShowTextInput = !isTaxRatesCountBelowThreshold;

const selectedOptions = selectedTaxRate
const {taxCode, taxValue} = currentTransaction ?? {};
const defaultTaxCode = getDefaultTaxCode(policy, currentTransaction) ?? '';
const effectiveTaxCode = taxCode && taxCode.length > 0 ? taxCode : defaultTaxCode;
const effectiveSelectedTaxRate = selectedTaxRate || (effectiveTaxCode ? (transformedTaxRates(policy, currentTransaction)[effectiveTaxCode]?.modifiedName ?? '') : '');
const hasTaxBeenDeleted = !!taxCode && taxValue !== undefined && !taxRates?.taxes?.[taxCode];
const hasTaxValueChanged = !!taxCode && taxValue !== undefined && taxRates?.taxes?.[taxCode]?.value !== taxValue;

const deletedTaxOption = !hasTaxBeenDeleted
? null
: {
code: undefined,
text: taxValue ?? '',
keyForList: taxCode ?? '',
searchText: taxValue ?? '',
tooltipText: taxValue ?? '',
isDisabled: true,
isSelected: true,
};

const selectedOptions = effectiveSelectedTaxRate
? [
{
modifiedName: selectedTaxRate,
modifiedName: effectiveSelectedTaxRate,
isDisabled: false,
accountID: null,
},
Expand All @@ -86,14 +105,26 @@ function TaxPicker({selectedTaxRate = '', policyID, transactionID, onSubmit, act
transaction: currentTransaction,
});

const selectedOptionKey = sections?.at(0)?.data?.find((taxRate) => taxRate.searchText === selectedTaxRate)?.keyForList;
const flattenedOptions = sections.flatMap((section) => section.data);
const selectedOptionKey =
flattenedOptions.find((taxRate) => taxRate.code === effectiveTaxCode)?.keyForList ?? flattenedOptions.find((taxRate) => taxRate.searchText === effectiveSelectedTaxRate)?.keyForList;

const handleSelectRow = (newSelectedOption: TaxRatesOption) => {
if (selectedOptionKey === newSelectedOption.keyForList) {
if (hasTaxValueChanged) {
onSubmit(newSelectedOption, !newSelectedOption.code);
return;
}

const isSameTaxCode = taxCode === newSelectedOption.code;
const currentTaxRateValue = taxCode ? taxRates?.taxes?.[taxCode]?.value : undefined;
const hasMatchingTaxValue = taxValue === undefined || currentTaxRateValue === taxValue;

if (isSameTaxCode && hasMatchingTaxValue) {
onDismiss();
return;
}
onSubmit(newSelectedOption);

onSubmit(newSelectedOption, hasTaxBeenDeleted);
};

const textInputOptions = {
Expand All @@ -103,9 +134,16 @@ function TaxPicker({selectedTaxRate = '', policyID, transactionID, onSubmit, act
headerMessage: getHeaderMessageForNonUserList((sections.at(0)?.data?.length ?? 0) > 0, searchValue),
};

const updatedSections = deletedTaxOption
? sections.map((section) => ({
...section,
data: [...section.data.filter((item) => item.code !== deletedTaxOption.code), deletedTaxOption],
Comment on lines 137 to +140

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Append the deleted tax only once

When the workspace still has at least CONST.STANDARD_LIST_ITEM_LIMIT enabled taxes, getTaxRatesSection() returns both a selected section and an all-items section (src/libs/TaxOptionsListUtils.ts:126-139). This code appends deletedTaxOption to every section, so the picker still renders the deleted tax twice in larger workspaces; the duplicate is only removed for the single-section case.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-03-18 at 21 52 44

}))
: sections;

return (
<SelectionListWithSections
sections={sections}
sections={updatedSections}
shouldShowTextInput={shouldShowTextInput}
textInputOptions={textInputOptions}
onSelectRow={handleSelectRow}
Expand Down
39 changes: 23 additions & 16 deletions src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,28 @@ function IOURequestStepTaxRatePage({
const currency = getCurrency(currentTransaction);
const decimals = getCurrencyDecimals(currency);

const updateTaxRates = (taxes: TaxRatesOption) => {
const updateTaxRates = (taxes: TaxRatesOption, shouldClearTax?: boolean) => {
const updateTaxRateParams = {
transactionID: currentTransaction?.transactionID,
transactionThreadReport: report,
parentReport,
taxCode: '',
taxValue: '',
taxAmount: 0,
policy,
policyTagList: policyTags,
policyCategories,
currentUserAccountIDParam,
currentUserEmailParam,
isASAPSubmitBetaEnabled,
parentReportNextStep,
};

if (shouldClearTax && isEditing) {
updateMoneyRequestTaxRate(updateTaxRateParams);
navigateBack();
return;
}
if (!currentTransaction || !taxes.code || !taxRates) {
Navigation.goBack();
return;
Expand All @@ -97,21 +118,7 @@ function IOURequestStepTaxRatePage({

if (isEditing) {
const newTaxCode = taxes.code;
updateMoneyRequestTaxRate({
transactionID: currentTransaction?.transactionID,
transactionThreadReport: report,
parentReport,
taxCode: newTaxCode,
taxValue,
taxAmount: convertToBackendAmount(taxAmount ?? 0),
policy,
policyTagList: policyTags,
policyCategories,
currentUserAccountIDParam,
currentUserEmailParam,
isASAPSubmitBetaEnabled,
parentReportNextStep,
});
updateMoneyRequestTaxRate({...updateTaxRateParams, taxCode: newTaxCode, taxValue, taxAmount: convertToBackendAmount(taxAmount ?? 0)});
navigateBack();
return;
}
Expand Down
Loading