From 4d5b18bdbc7df83b700089d4649de2003b6c374f Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 19 Jun 2024 00:34:30 +0530 Subject: [PATCH 01/14] Use suitable decimals while calculating tax --- src/components/MoneyRequestConfirmationList.tsx | 2 +- src/libs/CurrencyUtils.ts | 15 ++++++++------- src/libs/TransactionUtils.ts | 7 +++++-- .../iou/request/step/IOURequestStepAmount.tsx | 2 +- .../request/step/IOURequestStepDistanceRate.tsx | 1 + .../request/step/IOURequestStepTaxAmountPage.tsx | 2 +- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 87fc207dd1fd..5b2eca279d4e 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -389,7 +389,7 @@ function MoneyRequestConfirmationList({ taxCode = transaction?.taxCode ?? TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''; } const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxCode) ?? ''; - const taxAmount = TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount); + const taxAmount = TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, currency); const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount.toString())); IOU.setMoneyRequestTaxAmount(transaction?.transactionID ?? '', taxAmountInSmallestCurrencyUnits); }, [policy, shouldShowTax, previousTransactionAmount, previousTransactionCurrency, transaction, isDistanceRequest, customUnitRateID]); diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 7b54fbf0bed7..064c69799bb7 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -87,8 +87,9 @@ function convertToBackendAmount(amountAsFloat: number): number { * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmountAsInteger(amountAsInt: number): number { - return Math.trunc(amountAsInt) / 100.0; +function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string): number { + const decimals = getCurrencyDecimals(currency); + return toFixedNumber((Math.trunc(amountAsInt) / 100.0), decimals); } /** @@ -96,11 +97,11 @@ function convertToFrontendAmountAsInteger(amountAsInt: number): number { * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmountAsString(amountAsInt: number | null | undefined): string { +function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, currency: string): string { if (amountAsInt === null || amountAsInt === undefined) { return ''; } - return convertToFrontendAmountAsInteger(amountAsInt).toFixed(2); + return convertToFrontendAmountAsInteger(amountAsInt, currency).toFixed(2); } /** @@ -111,7 +112,7 @@ function convertToFrontendAmountAsString(amountAsInt: number | null | undefined) * @param currency - IOU currency */ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { - const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents, currency); /** * Fallback currency to USD if it empty string or undefined */ @@ -137,7 +138,7 @@ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURR * @param currency - IOU currency */ function convertToShortDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { - const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents, currency); return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', @@ -168,7 +169,7 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE * Acts the same as `convertAmountToDisplayString` but the result string does not contain currency */ function convertToDisplayStringWithoutCurrency(amountInCents: number, currency: string = CONST.CURRENCY.USD) { - const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents, currency); return NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index cfeb866e572f..5a83bd95c4ed 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -13,6 +13,7 @@ import DateUtils from './DateUtils'; import * as Localize from './Localize'; import * as NumberUtils from './NumberUtils'; import {getCleanedTagName, getCustomUnitRate} from './PolicyUtils'; +import {getCurrencyDecimals} from "@libs/CurrencyUtils"; let allTransactions: OnyxCollection = {}; Onyx.connect({ @@ -699,9 +700,11 @@ function hasWarningTypeViolation(transactionID: string, transactionViolations: O /** * Calculates tax amount from the given expense amount and tax percentage */ -function calculateTaxAmount(percentage: string, amount: number) { +function calculateTaxAmount(percentage: string, amount: number, currency: string | undefined = undefined) { const divisor = Number(percentage.slice(0, -1)) / 100 + 1; - return Math.round(amount - amount / divisor) / 100; + const taxAmount = (amount - amount / divisor) / 100; + const decimals = getCurrencyDecimals(currency); + return parseFloat(taxAmount.toFixed(decimals)); } /** diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 04c8e772844b..f234b3b71718 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -294,7 +294,7 @@ function IOURequestStepAmount({ const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, currentTransaction, currency) ?? ''; const taxCode = (currency !== transactionCurrency ? defaultTaxCode : transactionTaxCode) ?? defaultTaxCode; const taxPercentage = TransactionUtils.getTaxValue(policy, currentTransaction, taxCode) ?? ''; - const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, newAmount)); + const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, newAmount, currency)); if (isSplitBill) { IOU.setDraftSplitTransaction(transactionID, {amount: newAmount, currency, taxCode, taxAmount}); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 0f2d0cc0da61..f745cb1e89e8 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -23,6 +23,7 @@ import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; +import currency from "@src/types/onyx/Currency"; type IOURequestStepDistanceRateOnyxProps = { /** Policy details */ diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx index e67f83708938..d54cd6129e12 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx @@ -50,7 +50,7 @@ function getTaxAmount(transaction: OnyxEntry, policy: OnyxEntry Date: Wed, 26 Jun 2024 01:03:31 +0530 Subject: [PATCH 02/14] Update --- src/components/MoneyRequestAmountInput.tsx | 2 +- src/libs/CurrencyUtils.ts | 5 +++-- src/pages/iou/MoneyRequestAmountForm.tsx | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 0c3868312c41..ef03a5d4d717 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -103,7 +103,7 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: return {start: cursorPosition, end: cursorPosition}; }; -const defaultOnFormatAmount = (amount: number) => CurrencyUtils.convertToFrontendAmountAsString(amount); +const defaultOnFormatAmount = (amount: number, currency?: string) => CurrencyUtils.convertToFrontendAmountAsString(amount, currency ?? CONST.CURRENCY.USD); function MoneyRequestAmountInput( { diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 064c69799bb7..712d73569b0d 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -89,7 +89,7 @@ function convertToBackendAmount(amountAsFloat: number): number { */ function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string): number { const decimals = getCurrencyDecimals(currency); - return toFixedNumber((Math.trunc(amountAsInt) / 100.0), decimals); + return Number((Math.round(amountAsInt) / 100.0).toFixed(decimals)); } /** @@ -101,7 +101,8 @@ function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, if (amountAsInt === null || amountAsInt === undefined) { return ''; } - return convertToFrontendAmountAsInteger(amountAsInt, currency).toFixed(2); + const decimals = getCurrencyDecimals(currency); + return convertToFrontendAmountAsInteger(amountAsInt, currency).toFixed(decimals); } /** diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index ff061e7382c6..e911c332a5c7 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -70,8 +70,8 @@ type MoneyRequestAmountFormProps = { }; const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; -const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => - isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmountAsInteger(Math.abs(taxAmount)); +const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean, currency: string) => + isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmountAsInteger(Math.abs(taxAmount), currency); const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; @@ -149,7 +149,7 @@ function MoneyRequestAmountForm( }, [isFocused, wasFocused]); const initializeAmount = useCallback((newAmount: number) => { - const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount) : ''; + const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount, currency) : ''; moneyRequestAmountInput.current?.changeAmount(frontendAmount); moneyRequestAmountInput.current?.changeSelection({ start: frontendAmount.length, @@ -218,7 +218,7 @@ function MoneyRequestAmountForm( return; } - if (isTaxAmountInvalid(currentAmount, taxAmount, isTaxAmountForm)) { + if (isTaxAmountInvalid(currentAmount, taxAmount, isTaxAmountForm, currency)) { setFormError(translate('iou.error.invalidTaxAmount', {amount: formattedTaxAmount})); return; } From f1b4060af49c1b33c5927f38d48b57b45155681f Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 26 Jun 2024 01:17:00 +0530 Subject: [PATCH 03/14] Add and update tests --- tests/unit/CurrencyUtilsTest.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index 87b7c7ee4569..dc1a4888e9b4 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -113,7 +113,20 @@ describe('CurrencyUtils', () => { [2500, 25], [2500.5, 25], // The backend should never send a decimal .5 value ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount)).toBe(expectedResult); + expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount, CONST.CURRENCY.USD)).toBe(expectedResult); + }); + }); + + describe('convertToFrontendAmountAsInteger VND', () => { + test.each([ + [2500, 25], + [2550, 26], + [25, 0], + [2500, 25], + [2586, 26], + [2500.5, 25], // The backend should never send a decimal .5 value + ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount, 'VND')).toBe(expectedResult); }); }); @@ -127,7 +140,22 @@ describe('CurrencyUtils', () => { [undefined, ''], [0, '0.00'], ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmountAsString(input)).toBe(expectedResult); + expect(CurrencyUtils.convertToFrontendAmountAsString(input, CONST.CURRENCY.USD)).toBe(expectedResult); + }); + }); + + describe('convertToFrontendAmountAsString VND', () => { + test.each([ + [2500, '25'], + [2550, '26'], + [25, '0'], + [2500.5, '25'], + [null, ''], + [undefined, ''], + [0, '0'], + [2586, '26'] + ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsString(input, 'VND')).toBe(expectedResult); }); }); From c362173ef3bce5a5ec9903fd8efe1f4c47ec51c3 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 26 Jun 2024 01:19:25 +0530 Subject: [PATCH 04/14] Update IOURequestStepDistanceRate.tsx --- src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index f745cb1e89e8..0f2d0cc0da61 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -23,7 +23,6 @@ import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -import currency from "@src/types/onyx/Currency"; type IOURequestStepDistanceRateOnyxProps = { /** Policy details */ From e89431e2904b09e6849449194dd2a1ee3ee1b0a6 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:00:28 +0530 Subject: [PATCH 05/14] Update --- src/components/MoneyRequestConfirmationList.tsx | 2 +- src/libs/CurrencyUtils.ts | 4 ++-- src/libs/TransactionUtils.ts | 2 +- src/pages/iou/MoneyRequestAmountForm.tsx | 2 +- tests/unit/CurrencyUtilsTest.ts | 1 - 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 75114755e350..419b5374239b 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -332,7 +332,7 @@ function MoneyRequestConfirmationList({ const taxAmount = TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, currency); const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount.toString())); IOU.setMoneyRequestTaxAmount(transaction?.transactionID ?? '', taxAmountInSmallestCurrencyUnits); - }, [policy, shouldShowTax, previousTransactionAmount, previousTransactionCurrency, transaction, isDistanceRequest, customUnitRateID]); + }, [policy, shouldShowTax, previousTransactionAmount, previousTransactionCurrency, transaction, isDistanceRequest, customUnitRateID, currency]); // 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/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 712d73569b0d..6bbafe50268e 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -87,7 +87,7 @@ function convertToBackendAmount(amountAsFloat: number): number { * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string): number { +function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string = CONST.CURRENCY.USD): number { const decimals = getCurrencyDecimals(currency); return Number((Math.round(amountAsInt) / 100.0).toFixed(decimals)); } @@ -97,7 +97,7 @@ function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string) * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, currency: string): string { +function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, currency: string = CONST.CURRENCY.USD): string { if (amountAsInt === null || amountAsInt === undefined) { return ''; } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 8e0ad61eb020..b836b7bc3ec5 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -14,7 +14,7 @@ import * as Localize from './Localize'; import * as NumberUtils from './NumberUtils'; import Permissions from './Permissions'; import {getCleanedTagName, getCustomUnitRate} from './PolicyUtils'; -import {getCurrencyDecimals} from "@libs/CurrencyUtils"; +import {getCurrencyDecimals} from "./CurrencyUtils"; let allTransactions: OnyxCollection = {}; Onyx.connect({ diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index e911c332a5c7..f193773f9466 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -155,7 +155,7 @@ function MoneyRequestAmountForm( start: frontendAmount.length, end: frontendAmount.length, }); - }, []); + }, [currency]); useEffect(() => { if (!currency || typeof amount !== 'number') { diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index dc1a4888e9b4..b5b56806bc5c 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -122,7 +122,6 @@ describe('CurrencyUtils', () => { [2500, 25], [2550, 26], [25, 0], - [2500, 25], [2586, 26], [2500.5, 25], // The backend should never send a decimal .5 value ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { From f6d8de663216d716020b7cde61f251cf8cb03280 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:30:00 +0530 Subject: [PATCH 06/14] Update --- src/libs/CurrencyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 6bbafe50268e..f07407972c8e 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -89,7 +89,7 @@ function convertToBackendAmount(amountAsFloat: number): number { */ function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string = CONST.CURRENCY.USD): number { const decimals = getCurrencyDecimals(currency); - return Number((Math.round(amountAsInt) / 100.0).toFixed(decimals)); + return Number((Math.trunc(amountAsInt) / 100.0).toFixed(decimals)); } /** From 2a7588d22cdb7f5b068568ec95ec46282dc7fee6 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:31:23 +0530 Subject: [PATCH 07/14] Update --- src/libs/CurrencyUtils.ts | 2 +- src/libs/TransactionUtils.ts | 2 +- src/pages/iou/MoneyRequestAmountForm.tsx | 19 +++++++++++-------- tests/unit/CurrencyUtilsTest.ts | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index f07407972c8e..be7ce9aca8b5 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -97,7 +97,7 @@ function convertToFrontendAmountAsInteger(amountAsInt: number, currency: string * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, currency: string = CONST.CURRENCY.USD): string { +function convertToFrontendAmountAsString(amountAsInt: number | null | undefined, currency: string = CONST.CURRENCY.USD): string { if (amountAsInt === null || amountAsInt === undefined) { return ''; } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index b836b7bc3ec5..6d55792e57f1 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -9,12 +9,12 @@ import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {IOURequestType} from './actions/IOU'; import {isCorporateCard, isExpensifyCard} from './CardUtils'; +import {getCurrencyDecimals} from './CurrencyUtils'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; import * as NumberUtils from './NumberUtils'; import Permissions from './Permissions'; import {getCleanedTagName, getCustomUnitRate} from './PolicyUtils'; -import {getCurrencyDecimals} from "./CurrencyUtils"; let allTransactions: OnyxCollection = {}; Onyx.connect({ diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index f193773f9466..4db7a13171cb 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -148,14 +148,17 @@ function MoneyRequestAmountForm( }); }, [isFocused, wasFocused]); - const initializeAmount = useCallback((newAmount: number) => { - const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount, currency) : ''; - moneyRequestAmountInput.current?.changeAmount(frontendAmount); - moneyRequestAmountInput.current?.changeSelection({ - start: frontendAmount.length, - end: frontendAmount.length, - }); - }, [currency]); + const initializeAmount = useCallback( + (newAmount: number) => { + const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount, currency) : ''; + moneyRequestAmountInput.current?.changeAmount(frontendAmount); + moneyRequestAmountInput.current?.changeSelection({ + start: frontendAmount.length, + end: frontendAmount.length, + }); + }, + [currency], + ); useEffect(() => { if (!currency || typeof amount !== 'number') { diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index b5b56806bc5c..d6c18ad82471 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -152,7 +152,7 @@ describe('CurrencyUtils', () => { [null, ''], [undefined, ''], [0, '0'], - [2586, '26'] + [2586, '26'], ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { expect(CurrencyUtils.convertToFrontendAmountAsString(input, 'VND')).toBe(expectedResult); }); From ead55919064307bfaf92981761e69c159c13da90 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:20:46 +0530 Subject: [PATCH 08/14] Update --- src/libs/TransactionUtils.ts | 2 +- src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 2 +- src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx | 3 ++- tests/unit/CurrencyUtilsTest.ts | 8 ++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index b41f627bf23f..9f2c727b94ba 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -715,7 +715,7 @@ function hasWarningTypeViolation(transactionID: string, transactionViolations: O /** * Calculates tax amount from the given expense amount and tax percentage */ -function calculateTaxAmount(percentage: string, amount: number, currency: string | undefined = undefined) { +function calculateTaxAmount(percentage: string, amount: number, currency: string) { const divisor = Number(percentage.slice(0, -1)) / 100 + 1; const taxAmount = (amount - amount / divisor) / 100; const decimals = getCurrencyDecimals(currency); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 9399a2a65d9f..0d83bada93bb 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -81,7 +81,7 @@ function IOURequestStepDistanceRate({ const taxRateExternalID = policyCustomUnitRate?.attributes?.taxRateExternalID ?? '-1'; const taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistance(transaction)); const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxRateExternalID) ?? ''; - const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount)); + const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, rates[customUnitRateID].currency)); IOU.setMoneyRequestTaxAmount(transactionID, taxAmount); IOU.setMoneyRequestTaxRate(transactionID, taxRateExternalID); } diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index 22ca585ac4b1..6025a573e417 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -17,6 +17,7 @@ import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; +import {getCurrency, transformedTaxRates} from "@libs/TransactionUtils"; type IOURequestStepTaxRatePageOnyxProps = { policy: OnyxEntry; @@ -38,7 +39,7 @@ function getTaxAmount(policy: OnyxEntry, transaction: OnyxEntry TransactionUtils.getTaxValue(policy, transaction, taxCode); const taxPercentage = getTaxValue(selectedTaxCode); if (taxPercentage) { - return TransactionUtils.calculateTaxAmount(taxPercentage, amount); + return TransactionUtils.calculateTaxAmount(taxPercentage, amount, getCurrency(transaction)); } } diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index d6c18ad82471..20b03ea07c81 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -117,6 +117,10 @@ describe('CurrencyUtils', () => { }); }); + /** + * VND uses 0 decimals, so this test is needed. + * https://github.com/Expensify/App/pull/43948 + */ describe('convertToFrontendAmountAsInteger VND', () => { test.each([ [2500, 25], @@ -143,6 +147,10 @@ describe('CurrencyUtils', () => { }); }); + /** + * VND uses 0 decimals, so this test is needed. + * https://github.com/Expensify/App/pull/43948 + */ describe('convertToFrontendAmountAsString VND', () => { test.each([ [2500, '25'], From 21530f8b9693fded2358d7df37cd73536e183ee7 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:40:53 +0530 Subject: [PATCH 09/14] Update IOURequestStepTaxRatePage.tsx --- src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index 6025a573e417..d349545d61db 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -13,11 +13,11 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {Policy, PolicyCategories, PolicyTagList, Transaction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {getCurrency} from "@libs/TransactionUtils"; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; -import {getCurrency, transformedTaxRates} from "@libs/TransactionUtils"; type IOURequestStepTaxRatePageOnyxProps = { policy: OnyxEntry; From 2fb7b2662043a00269271ebfa4ab1380135171eb Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 4 Jul 2024 21:26:31 +0530 Subject: [PATCH 10/14] Add some default values --- src/pages/iou/request/step/IOURequestStepAmount.tsx | 2 +- src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index f234b3b71718..a64af40a32a5 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -294,7 +294,7 @@ function IOURequestStepAmount({ const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, currentTransaction, currency) ?? ''; const taxCode = (currency !== transactionCurrency ? defaultTaxCode : transactionTaxCode) ?? defaultTaxCode; const taxPercentage = TransactionUtils.getTaxValue(policy, currentTransaction, taxCode) ?? ''; - const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, newAmount, currency)); + const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, newAmount, currency ?? CONST.CURRENCY.USD)); if (isSplitBill) { IOU.setDraftSplitTransaction(transactionID, {amount: newAmount, currency, taxCode, taxAmount}); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 0d83bada93bb..4f70d3e4fee9 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -81,7 +81,7 @@ function IOURequestStepDistanceRate({ const taxRateExternalID = policyCustomUnitRate?.attributes?.taxRateExternalID ?? '-1'; const taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistance(transaction)); const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxRateExternalID) ?? ''; - const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, rates[customUnitRateID].currency)); + const taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, rates[customUnitRateID].currency ?? CONST.CURRENCY.USD)); IOU.setMoneyRequestTaxAmount(transactionID, taxAmount); IOU.setMoneyRequestTaxRate(transactionID, taxRateExternalID); } From 6747545d35b2a020293671d564087bfaa9bb47e1 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 4 Jul 2024 21:28:24 +0530 Subject: [PATCH 11/14] Lint fix --- src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index d349545d61db..a14c3a3a50be 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -7,13 +7,13 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {TaxRatesOption} from '@libs/OptionsListUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import {getCurrency} from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {Policy, PolicyCategories, PolicyTagList, Transaction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {getCurrency} from "@libs/TransactionUtils"; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; From f05fff30fce929ec48a8af340dc3cd078456f8c5 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 5 Jul 2024 14:21:20 +0530 Subject: [PATCH 12/14] Typecheck fix --- src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx index d54cd6129e12..4b3b5cc13eb1 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.tsx @@ -50,7 +50,7 @@ function getTaxAmount(transaction: OnyxEntry, policy: OnyxEntry Date: Fri, 5 Jul 2024 15:39:15 +0530 Subject: [PATCH 13/14] Update --- tests/unit/CurrencyUtilsTest.ts | 84 +++++++++++++-------------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index 20b03ea07c81..b4d09104a74c 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -107,62 +107,44 @@ describe('CurrencyUtils', () => { describe('convertToFrontendAmountAsInteger', () => { test.each([ - [2500, 25], - [2550, 25.5], - [25, 0.25], - [2500, 25], - [2500.5, 25], // The backend should never send a decimal .5 value - ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount, CONST.CURRENCY.USD)).toBe(expectedResult); - }); - }); - - /** - * VND uses 0 decimals, so this test is needed. - * https://github.com/Expensify/App/pull/43948 - */ - describe('convertToFrontendAmountAsInteger VND', () => { - test.each([ - [2500, 25], - [2550, 26], - [25, 0], - [2586, 26], - [2500.5, 25], // The backend should never send a decimal .5 value - ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount, 'VND')).toBe(expectedResult); + [2500, 25, 'USD'], + [2550, 25.5, 'USD'], + [25, 0.25, 'USD'], + [2500, 25, 'USD'], + [2500.5, 25, 'USD'], // The backend should never send a decimal .5 value + // VND uses 0 decimals. + // https://github.com/Expensify/App/pull/43948 + [2500, 25, 'VND'], + [2550, 26, 'VND'], + [25, 0, 'VND'], + [2586, 26, 'VND'], + [2500.5, 25, 'VND'], // The backend should never send a decimal .5 value + ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult, currency) => { + expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount, currency)).toBe(expectedResult); }); }); describe('convertToFrontendAmountAsString', () => { test.each([ - [2500, '25.00'], - [2550, '25.50'], - [25, '0.25'], - [2500.5, '25.00'], - [null, ''], - [undefined, ''], - [0, '0.00'], - ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmountAsString(input, CONST.CURRENCY.USD)).toBe(expectedResult); - }); - }); - - /** - * VND uses 0 decimals, so this test is needed. - * https://github.com/Expensify/App/pull/43948 - */ - describe('convertToFrontendAmountAsString VND', () => { - test.each([ - [2500, '25'], - [2550, '26'], - [25, '0'], - [2500.5, '25'], - [null, ''], - [undefined, ''], - [0, '0'], - [2586, '26'], - ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmountAsString(input, 'VND')).toBe(expectedResult); + [2500, '25.00', 'USD'], + [2550, '25.50', 'USD'], + [25, '0.25', 'USD'], + [2500.5, '25.00', 'USD'], + [null, '', 'USD'], + [undefined, '', 'USD'], + [0, '0.00', 'USD'], + // VND uses 0 decimals. + // https://github.com/Expensify/App/pull/43948 + [2500, '25', 'VND'], + [2550, '26', 'VND'], + [25, '0', 'VND'], + [2500.5, '25', 'VND'], + [null, '', 'VND'], + [undefined, '', 'VND'], + [0, '0', 'VND'], + [2586, '26', 'VND'], + ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult, currency) => { + expect(CurrencyUtils.convertToFrontendAmountAsString(input, currency ?? CONST.CURRENCY.USD)).toBe(expectedResult); }); }); From db546d937084b03c6dc74e6f6c21d455495a443f Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Sat, 6 Jul 2024 14:52:53 +0530 Subject: [PATCH 14/14] Update --- tests/unit/CurrencyUtilsTest.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index b4d09104a74c..5322faff763f 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -112,8 +112,6 @@ describe('CurrencyUtils', () => { [25, 0.25, 'USD'], [2500, 25, 'USD'], [2500.5, 25, 'USD'], // The backend should never send a decimal .5 value - // VND uses 0 decimals. - // https://github.com/Expensify/App/pull/43948 [2500, 25, 'VND'], [2550, 26, 'VND'], [25, 0, 'VND'], @@ -133,8 +131,6 @@ describe('CurrencyUtils', () => { [null, '', 'USD'], [undefined, '', 'USD'], [0, '0.00', 'USD'], - // VND uses 0 decimals. - // https://github.com/Expensify/App/pull/43948 [2500, '25', 'VND'], [2550, '26', 'VND'], [25, '0', 'VND'],