diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b18a98b13304..dff80f45e3f9 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -178,9 +178,11 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & type MoneyRequestConfirmationListItem = Participant | ReportUtils.OptionData; -const getTaxAmount = (transaction: OnyxEntry, policy: OnyxEntry) => { +const getTaxAmount = (transaction: OnyxEntry, policy: OnyxEntry, isDistanceRequest: boolean) => { + if (isDistanceRequest) { + return DistanceRequestUtils.calculateTaxAmount(policy, transaction, TransactionUtils.getRateID(transaction) ?? ''); + } const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''; - const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, transaction?.taxCode ?? defaultTaxCode) ?? ''; return TransactionUtils.calculateTaxAmount(taxPercentage, transaction?.amount ?? 0); }; @@ -371,7 +373,7 @@ function MoneyRequestConfirmationList({ // Calculate and set tax amount in transaction draft useEffect(() => { - const taxAmount = getTaxAmount(transaction, policy).toString(); + const taxAmount = getTaxAmount(transaction, policy, isDistanceRequest).toString(); const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); if (transaction?.taxAmount && previousTransactionAmount === transaction?.amount && previousTransactionCurrency === transaction?.currency) { @@ -379,7 +381,7 @@ function MoneyRequestConfirmationList({ } IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true); - }, [policy, transaction, transactionID, previousTransactionAmount, previousTransactionCurrency]); + }, [policy, transaction, transactionID, previousTransactionAmount, previousTransactionCurrency, isDistanceRequest]); // If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again if (isEditingSplitBill && didConfirm) { diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 9cb48534214e..230ad8deb130 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -4,13 +4,14 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {RateAndUnit} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {LastSelectedDistanceRates, Report} from '@src/types/onyx'; +import type {LastSelectedDistanceRates, Report, Transaction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import * as CurrencyUtils from './CurrencyUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportUtils from './ReportUtils'; +import * as TransactionUtils from './TransactionUtils'; type MileageRate = { customUnitRateID?: string; @@ -53,7 +54,7 @@ function getDefaultMileageRate(policy: OnyxEntry | EmptyObject): Mileage return null; } - const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceUnit = PolicyUtils.getCustomUnit(policy); if (!distanceUnit?.rates) { return null; } @@ -193,7 +194,7 @@ function getMileageRates(policy: OnyxEntry): Record return mileageRates; } - const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceUnit = PolicyUtils.getCustomUnit(policy); if (!distanceUnit?.rates) { return mileageRates; } @@ -266,6 +267,24 @@ function getCustomUnitRateID(reportID: string) { return customUnitRateID; } +function calculateTaxAmount(policy: OnyxEntry, transaction: OnyxEntry, customUnitRateID: string) { + const distanceUnit = PolicyUtils.getCustomUnit(policy); + const customUnitID = distanceUnit?.customUnitID; + if (!policy?.customUnits || !customUnitID) { + return 0; + } + const policyCustomUnitRate = policy?.customUnits[customUnitID].rates[customUnitRateID]; + const unit = policy?.customUnits[customUnitID]?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; + const rate = policyCustomUnitRate?.rate ?? 0; + const distance = TransactionUtils.getDistance(transaction); + const amount = getDistanceRequestAmount(distance, unit, rate); + const taxClaimablePercentage = policyCustomUnitRate.attributes?.taxClaimablePercentage ?? 0; + const taxRateExternalID = policyCustomUnitRate.attributes?.taxRateExternalID ?? ''; + const taxableAmount = amount * taxClaimablePercentage; + const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxRateExternalID) ?? ''; + return TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount); +} + export default { getDefaultMileageRate, getDistanceMerchant, @@ -276,6 +295,7 @@ export default { getRateForP2P, getCustomUnitRateID, convertToDistanceInMeters, + calculateTaxAmount, }; export type {MileageRate}; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index fd128fde19d9..553e775ba99f 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -90,6 +90,13 @@ function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => return numValue.toFixed(CONST.CUSTOM_UNITS.RATE_DECIMALS); } +/** + * Retrieves the distance custom unit object for the given policy + */ +function getCustomUnit(policy: OnyxEntry | EmptyObject) { + return Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); +} + function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => string): string { const numValue = getNumericValue(value, toLocaleDigit); if (Number.isNaN(numValue)) { @@ -278,7 +285,7 @@ function isPaidGroupPolicy(policy: OnyxEntry | EmptyObject): boolean { } function isTaxTrackingEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry, isDistanceRequest: boolean): boolean { - const distanceUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceUnit = getCustomUnit(policy); const customUnitID = distanceUnit?.customUnitID ?? 0; const isPolicyTaxTrackingEnabled = isPolicyExpenseChat && policy?.tax?.trackingEnabled; const isTaxEnabledForDistance = isPolicyTaxTrackingEnabled && policy?.customUnits?.[customUnitID]?.attributes?.taxEnabled; @@ -505,6 +512,7 @@ export { findCurrentXeroOrganization, getCurrentXeroOrganizationName, getXeroBankAccountsWithDefaultSelect, + getCustomUnit, sortWorkspacesBySelected, }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 92d843c96ea3..4d918352ba91 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1567,14 +1567,14 @@ function clearAvatarErrors(policyID: string) { */ function updateGeneralSettings(policyID: string, name: string, currencyValue?: string) { const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; - const distanceUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - const customUnitID = distanceUnit?.customUnitID; - const currency = currencyValue ?? policy?.outputCurrency ?? CONST.CURRENCY.USD; - if (!policy) { return; } + const distanceUnit = PolicyUtils.getCustomUnit(policy); + const customUnitID = distanceUnit?.customUnitID; + const currency = currencyValue ?? policy?.outputCurrency ?? CONST.CURRENCY.USD; + const currentRates = distanceUnit?.rates ?? {}; const optimisticRates: Record = {}; const finallyRates: Record = {}; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 80ddd6567de8..762db0ab811c 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -11,7 +11,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {isTaxTrackingEnabled} from '@libs/PolicyUtils'; +import {getCustomUnit, isTaxTrackingEnabled} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import CONST from '@src/CONST'; @@ -50,7 +50,7 @@ function IOURequestStepDistanceRate({ const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); - const distanceUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceUnit = getCustomUnit(policy); const customUnitID = distanceUnit?.customUnitID; const isPolicyExpenseChat = ReportUtils.isReportInGroupPolicy(report); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); @@ -78,12 +78,10 @@ function IOURequestStepDistanceRate({ const initiallyFocusedOption = sections.find((item) => item.isSelected)?.keyForList; function selectDistanceRate(customUnitRateID: string) { - if (transaction?.amount && policy?.customUnits && customUnitID && shouldShowTax) { - const taxClaimablePercentage = policy?.customUnits[customUnitID].rates[customUnitRateID].attributes?.taxClaimablePercentage ?? 0; - const taxRateExternalID = policy?.customUnits[customUnitID].rates[customUnitRateID].attributes?.taxRateExternalID ?? ''; - const taxableAmount = -1 * transaction.amount * taxClaimablePercentage; - const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxRateExternalID) ?? ''; - const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount)); + if (policy?.customUnits && customUnitID && shouldShowTax) { + const policyCustomUnitRate = policy?.customUnits[customUnitID].rates[customUnitRateID]; + const taxRateExternalID = policyCustomUnitRate.attributes?.taxRateExternalID ?? ''; + const taxAmount = CurrencyUtils.convertToBackendAmount(DistanceRequestUtils.calculateTaxAmount(policy, transaction, customUnitRateID)); IOU.setMoneyRequestTaxAmount(transactionID, taxAmount, true); IOU.setMoneyRequestTaxRate(transactionID, taxRateExternalID); } diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx index 6faf23af289e..4e1821c6aad6 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx @@ -13,6 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; import {getUnitTranslationKey} from '@libs/WorkspacesSettingsUtils'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; @@ -62,7 +63,7 @@ function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { }, [props]); const saveUnitAndRate = (newUnit: Unit, newRate: string) => { - const distanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomUnit = PolicyUtils.getCustomUnit(props.policy); if (!distanceCustomUnit) { return; } @@ -82,7 +83,7 @@ function WorkspaceRateAndUnitPage(props: WorkspaceRateAndUnitPageProps) { Policy.updateWorkspaceCustomUnitAndRate(props.policy?.id ?? '', distanceCustomUnit, newCustomUnit, props.policy?.lastModified); }; - const distanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomUnit = PolicyUtils.getCustomUnit(props.policy); const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const unitValue = props.workspaceRateAndUnit?.unit ?? distanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx index 9c7eaa82167f..1acd3f501314 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage.tsx @@ -10,6 +10,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import {validateRateValue} from '@libs/PolicyDistanceRatesUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; import withPolicy from '@pages/workspace/withPolicy'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; @@ -53,10 +54,10 @@ function WorkspaceRatePage(props: WorkspaceRatePageProps) { ); const defaultValue = useMemo(() => { - const defaultDistanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const defaultDistanceCustomUnit = PolicyUtils.getCustomUnit(props.policy); const distanceCustomRate = Object.values(defaultDistanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); return distanceCustomRate?.rate ?? 0; - }, [props.policy?.customUnits]); + }, [props.policy]); return ( { - const defaultDistanceCustomUnit = Object.values(props.policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const defaultDistanceCustomUnit = PolicyUtils.getCustomUnit(props.policy); return defaultDistanceCustomUnit?.attributes.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; - }, [props.policy?.customUnits]); + }, [props.policy]); return ( (''); const {isSmallScreenWidth} = useWindowDimensions(); const viewAllReceiptsUrl = `expenses?policyIDList=${policy?.id ?? ''}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; - const distanceCustomUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const distanceCustomUnit = PolicyUtils.getCustomUnit(policy); const distanceCustomRate = Object.values(distanceCustomUnit?.rates ?? {}).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE); const {translate, toLocaleDigit} = useLocalize(); const {isOffline} = useNetwork();