Skip to content

Commit 324a739

Browse files
authored
Merge pull request Expensify#81888 from callstack-internal/fix/77631-tax-rate-mismatch
Fix tax rates values mismatch
2 parents a8fe670 + 8c875ff commit 324a739

9 files changed

Lines changed: 247 additions & 22 deletions

File tree

src/components/MoneyRequestConfirmationList.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
getTaxValue,
5454
hasMissingSmartscanFields,
5555
hasRoute as hasRouteUtil,
56+
hasTaxRateWithMatchingValue,
5657
isMerchantMissing,
5758
isScanRequest as isScanRequestUtil,
5859
} from '@libs/TransactionUtils';
@@ -439,6 +440,7 @@ function MoneyRequestConfirmationList({
439440
category: iouCategory,
440441
tag: getTag(transaction),
441442
taxCode: transaction?.taxCode,
443+
taxValue: transaction?.taxValue,
442444
policyCategories,
443445
policyTagLists: policyTags,
444446
policyTaxRates: policy?.taxRates?.taxes,
@@ -1017,7 +1019,7 @@ function MoneyRequestConfirmationList({
10171019
return;
10181020
}
10191021

1020-
if (shouldShowTax && !!transaction.taxCode && !Object.keys(policy?.taxRates?.taxes ?? {}).some((key) => key === transaction.taxCode)) {
1022+
if (shouldShowTax && !!transaction.taxCode && !hasTaxRateWithMatchingValue(policy, transaction)) {
10211023
setFormError('violations.taxOutOfPolicy');
10221024
return;
10231025
}

src/components/MoneyRequestConfirmationListFooter.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {getTagVisibility, hasEnabledTags} from '@libs/TagsOptionsListUtils';
3434
import {
3535
getTagForDisplay,
3636
getTaxAmount,
37-
getTaxName,
37+
getTaxRateTitle,
3838
isAmountMissing,
3939
isCreatedMissing,
4040
isFetchingWaypointsFromServer,
@@ -418,14 +418,8 @@ function MoneyRequestConfirmationListFooter({
418418
const taxAmount = getTaxAmount(transaction, false);
419419
const formattedTaxAmount = convertToDisplayString(taxAmount, iouCurrencyCode);
420420
// Get the tax rate title based on the policy and transaction
421-
let taxRateTitle;
422-
if (getTaxName(policy, transaction)) {
423-
taxRateTitle = getTaxName(policy, transaction);
424-
} else if (isMovingCurrentTransactionFromTrackExpense) {
425-
taxRateTitle = getTaxName(policyForMovingExpenses, transaction);
426-
} else {
427-
taxRateTitle = '';
428-
}
421+
const taxRateTitle = getTaxRateTitle(policy, transaction, isMovingCurrentTransactionFromTrackExpense, policyForMovingExpenses);
422+
429423
// Determine if the merchant error should be displayed
430424
const shouldDisplayMerchantError = isMerchantRequired && (shouldDisplayFieldError || formError === 'iou.error.invalidMerchant') && isMerchantEmpty;
431425
const shouldDisplayDistanceRateError = formError === 'iou.error.invalidRate';

src/libs/TransactionUtils/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,6 +2180,47 @@ function getTaxName(policy: OnyxEntry<Policy>, transaction: OnyxEntry<Transactio
21802180
return taxRate?.modifiedName;
21812181
}
21822182

2183+
/**
2184+
* Checks if the tax rate with matching transaction's tax rate value exists in the policy tax rates
2185+
*/
2186+
function hasTaxRateWithMatchingValue(policy: OnyxEntry<Policy>, transaction: OnyxEntry<Transaction>) {
2187+
if (!policy || !transaction) {
2188+
return false;
2189+
}
2190+
2191+
const transactionTaxCode = getTaxCode(transaction);
2192+
const transformedRates = transformedTaxRates(policy, transaction);
2193+
const taxRate = Object.values(transformedRates).find((rate) => rate.code === transactionTaxCode);
2194+
2195+
if (!transaction?.taxValue) {
2196+
return !!taxRate;
2197+
}
2198+
2199+
return taxRate?.value === transaction?.taxValue;
2200+
}
2201+
2202+
/**
2203+
* Gets the tax rate title for display, handling the case when moving expenses from track to submit
2204+
*/
2205+
function getTaxRateTitle(policy: OnyxEntry<Policy>, transaction: OnyxEntry<Transaction>, isMovingFromTrackExpense: boolean, policyForMovingExpenses?: OnyxEntry<Policy>): string {
2206+
const currentTaxName = getTaxName(policy, transaction);
2207+
2208+
if (currentTaxName) {
2209+
// If moving from track expense show the tax name from the moving policy
2210+
if (isMovingFromTrackExpense && !hasTaxRateWithMatchingValue(policy, transaction)) {
2211+
return getTaxName(policyForMovingExpenses, transaction) ?? '';
2212+
}
2213+
return getTaxName(policy, transaction, true) ?? '';
2214+
}
2215+
2216+
// If no tax name on current policy but moving from track expense, use the moving policy
2217+
if (isMovingFromTrackExpense) {
2218+
return getTaxName(policyForMovingExpenses, transaction, true) ?? '';
2219+
}
2220+
2221+
return '';
2222+
}
2223+
21832224
type FieldsToCompare = Record<string, Array<keyof Transaction>>;
21842225
type FieldsToChange = {
21852226
category?: Array<string | undefined>;
@@ -2758,6 +2799,8 @@ export {
27582799
transformedTaxRates,
27592800
getTaxValue,
27602801
getTaxName,
2802+
getTaxRateTitle,
2803+
hasTaxRateWithMatchingValue,
27612804
getEnabledTaxRateCount,
27622805
getUpdatedTransaction,
27632806
getClearedPendingFields,

src/libs/Violations/ViolationsUtils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ function extractErrorMessages(errors: Errors | ReceiptErrors, errorActions: Repo
247247
* Returns true if the violation should be cleared, false if it should persist.
248248
*/
249249
function getIsViolationFixed(violationError: string, params: ViolationFixParams): boolean {
250-
const {category, tag, taxCode, policyCategories, policyTagLists, policyTaxRates, iouAttendees, currentUserPersonalDetails, isAttendeeTrackingEnabled, isControlPolicy} = params;
250+
const {category, tag, taxCode, taxValue, policyCategories, policyTagLists, policyTaxRates, iouAttendees, currentUserPersonalDetails, isAttendeeTrackingEnabled, isControlPolicy} = params;
251251

252252
const violationValidators: Record<string, () => boolean> = {
253253
[`${CONST.VIOLATIONS_PREFIX}${CONST.VIOLATIONS.CATEGORY_OUT_OF_POLICY}`]: () => {
@@ -267,8 +267,15 @@ function getIsViolationFixed(violationError: string, params: ViolationFixParams)
267267
return hasEnabledTags && hasMatchingTag;
268268
},
269269
[`${CONST.VIOLATIONS_PREFIX}${CONST.VIOLATIONS.TAX_OUT_OF_POLICY}`]: () => {
270-
// Tax is fixed if it's empty or exists in policy tax rates
271-
return !taxCode || Object.keys(policyTaxRates ?? {}).some((key) => key === taxCode);
270+
if (!taxCode || !policyTaxRates) {
271+
return !taxCode;
272+
}
273+
const matchingTaxRate = policyTaxRates[taxCode];
274+
if (!matchingTaxRate) {
275+
return false;
276+
}
277+
// If taxValue is provided, check that it matches the policy tax rate. If taxValue is not provided, just check that the tax code exists in the policy.
278+
return taxValue !== undefined ? matchingTaxRate.value === taxValue : true;
272279
},
273280
[`${CONST.VIOLATIONS_PREFIX}${CONST.VIOLATIONS.MISSING_ATTENDEES}`]: () => {
274281
// Attendees violation is fixed if getIsMissingAttendeesViolation returns false

src/libs/Violations/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import type {PolicyCategories, PolicyTagLists} from '@src/types/onyx';
1+
import type {PolicyCategories, PolicyTagLists, TaxRates} from '@src/types/onyx';
22
import type {Attendee} from '@src/types/onyx/IOU';
33
import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails';
44

55
type ViolationFixParams = {
66
category: string;
77
tag: string;
88
taxCode: string | undefined;
9+
/** Tax value as a percentage string (e.g., "5%", "10%") or undefined if not set */
10+
taxValue?: string;
911
policyCategories: PolicyCategories | undefined;
1012
policyTagLists: PolicyTagLists | undefined;
11-
policyTaxRates: Record<string, unknown> | undefined;
13+
policyTaxRates: TaxRates | undefined;
1214
iouAttendees: Attendee[] | undefined;
1315
currentUserPersonalDetails: CurrentUserPersonalDetails;
1416
isAttendeeTrackingEnabled: boolean | undefined;

src/libs/actions/IOU/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12383,6 +12383,16 @@ function setMoneyRequestTaxAmount(transactionID: string, taxAmount: number | nul
1238312383
Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {taxAmount});
1238412384
}
1238512385

12386+
type TaxRateValues = {
12387+
taxCode: string | null;
12388+
taxAmount: number | null;
12389+
taxValue: string | null;
12390+
};
12391+
12392+
function setMoneyRequestTaxRateValues(transactionID: string, taxRateValues: TaxRateValues, isDraft = true) {
12393+
Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {...taxRateValues});
12394+
}
12395+
1238612396
// eslint-disable-next-line rulesdir/no-negated-variables
1238712397
function navigateToStartStepIfScanFileCannotBeRead(
1238812398
receiptFilename: string | undefined,
@@ -12951,7 +12961,7 @@ function prepareRejectMoneyRequestData(
1295112961
movedToReport = existingOpenReport;
1295212962
rejectedToReportID = existingOpenReport.reportID;
1295312963

12954-
const [, , iouAction, ,] = buildOptimisticMoneyRequestEntities({
12964+
const [, , iouAction] = buildOptimisticMoneyRequestEntities({
1295512965
iouReport: movedToReport,
1295612966
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
1295712967
amount: transactionAmount,
@@ -13042,7 +13052,7 @@ function prepareRejectMoneyRequestData(
1304213052
reportTransactions,
1304313053
betas,
1304413054
});
13045-
const [, createdActionForExpenseReport, iouAction, ,] = buildOptimisticMoneyRequestEntities({
13055+
const [, createdActionForExpenseReport, iouAction] = buildOptimisticMoneyRequestEntities({
1304613056
iouReport: newExpenseReport,
1304713057
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
1304813058
amount: transactionAmount,
@@ -13840,6 +13850,7 @@ export {
1384013850
setMoneyRequestTag,
1384113851
setMoneyRequestTaxAmount,
1384213852
setMoneyRequestTaxRate,
13853+
setMoneyRequestTaxRateValues,
1384313854
startMoneyRequest,
1384413855
submitReport,
1384513856
trackExpense,

src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'
66
import useLocalize from '@hooks/useLocalize';
77
import useOnyx from '@hooks/useOnyx';
88
import usePermissions from '@hooks/usePermissions';
9+
import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses';
910
import usePolicyForTransaction from '@hooks/usePolicyForTransaction';
1011
import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure';
1112
import {convertToBackendAmount} from '@libs/CurrencyUtils';
1213
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
14+
import {isMovingTransactionFromTrackExpense} from '@libs/IOUUtils';
1315
import Navigation from '@libs/Navigation/Navigation';
1416
import type {TaxRatesOption} from '@libs/TaxOptionsListUtils';
15-
import {calculateTaxAmount, getAmount, getCurrency, getTaxName, getTaxValue} from '@libs/TransactionUtils';
16-
import {setMoneyRequestTaxAmount, setMoneyRequestTaxRate, updateMoneyRequestTaxRate} from '@userActions/IOU';
17+
import {calculateTaxAmount, getAmount, getCurrency, getTaxRateTitle, getTaxValue} from '@libs/TransactionUtils';
18+
import {setMoneyRequestTaxRateValues, updateMoneyRequestTaxRate} from '@userActions/IOU';
1719
import {setDraftSplitTransaction} from '@userActions/IOU/Split';
1820
import CONST from '@src/CONST';
1921
import ONYXKEYS from '@src/ONYXKEYS';
@@ -62,14 +64,15 @@ function IOURequestStepTaxRatePage({
6264
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
6365
const currentUserAccountIDParam = currentUserPersonalDetails.accountID;
6466
const currentUserEmailParam = currentUserPersonalDetails.login ?? '';
67+
const {policyForMovingExpenses} = usePolicyForMovingExpenses();
6568
const {isBetaEnabled} = usePermissions();
6669
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
6770

6871
const navigateBack = () => {
6972
Navigation.goBack(backTo);
7073
};
7174

72-
const taxRateTitle = getTaxName(policy, currentTransaction);
75+
const taxRateTitle = getTaxRateTitle(policy, currentTransaction, isMovingTransactionFromTrackExpense(action), policyForMovingExpenses);
7376
const currency = getCurrency(currentTransaction);
7477
const decimals = getCurrencyDecimals(currency);
7578

@@ -86,6 +89,7 @@ function IOURequestStepTaxRatePage({
8689
setDraftSplitTransaction(currentTransaction.transactionID, splitDraftTransaction, {
8790
taxAmount: convertToBackendAmount(taxAmount ?? 0),
8891
taxCode: taxes.code,
92+
taxValue,
8993
});
9094
navigateBack();
9195
return;
@@ -117,8 +121,8 @@ function IOURequestStepTaxRatePage({
117121
return;
118122
}
119123
const amountInSmallestCurrencyUnits = convertToBackendAmount(taxAmount);
120-
setMoneyRequestTaxRate(currentTransaction?.transactionID, taxes?.code ?? '');
121-
setMoneyRequestTaxAmount(currentTransaction.transactionID, amountInSmallestCurrencyUnits);
124+
125+
setMoneyRequestTaxRateValues(currentTransaction.transactionID, {taxCode: taxes?.code ?? '', taxAmount: amountInSmallestCurrencyUnits, taxValue});
122126

123127
navigateBack();
124128
};

0 commit comments

Comments
 (0)