diff --git a/src/CONST.ts b/src/CONST.ts
index 2b3cc7c09708..3b660997dbb7 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1827,6 +1827,8 @@ const CONST = {
RECEIPT: 'receipt',
DISTANCE: 'distance',
TAG: 'tag',
+ TAX_RATE: 'taxRate',
+ TAX_AMOUNT: 'taxAmount',
},
FOOTER: {
EXPENSE_MANAGEMENT_URL: `${USE_EXPENSIFY_URL}/expense-management`,
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index afc0c7b703dc..546b2885e24f 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -10,6 +10,7 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
+import usePrevious from '@hooks/usePrevious';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
@@ -21,6 +22,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
+import {isTaxPolicyEnabled} from '@libs/PolicyUtils';
import * as ReceiptUtils from '@libs/ReceiptUtils';
import * as ReportUtils from '@libs/ReportUtils';
import playSound, {SOUNDS} from '@libs/Sound';
@@ -204,6 +206,11 @@ const defaultProps = {
isPolicyExpenseChat: false,
};
+const getTaxAmount = (transaction, defaultTaxValue) => {
+ const percentage = (transaction.taxRate ? transaction.taxRate.data.value : defaultTaxValue) || '';
+ return TransactionUtils.calculateTaxAmount(percentage, transaction.amount);
+};
+
function MoneyTemporaryForRefactorRequestConfirmationList({
bankAccountRoute,
canModifyParticipants,
@@ -277,7 +284,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
const shouldShowTags = useMemo(() => isPolicyExpenseChat && OptionsListUtils.hasEnabledTags(policyTagLists), [isPolicyExpenseChat, policyTagLists]);
// A flag for showing tax rate
- const shouldShowTax = isPolicyExpenseChat && policy && lodashGet(policy, 'tax.trackingEnabled', policy.isTaxTrackingEnabled);
+ const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy);
// A flag for showing the billable field
const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true);
@@ -292,9 +299,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
);
const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode);
- const defaultTaxKey = taxRates.defaultExternalID;
- const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || '';
- const taxRateTitle = (transaction.taxRate && transaction.taxRate.text) || defaultTaxName;
+ const taxRateTitle = TransactionUtils.getDefaultTaxName(taxRates, transaction);
+
+ const previousTransactionAmount = usePrevious(transaction.amount);
const isFocused = useIsFocused();
const [formError, setFormError] = useState('');
@@ -362,6 +369,18 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
IOU.setMoneyRequestAmount_temporaryForRefactor(transaction.transactionID, amount, currency);
}, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]);
+ // Calculate and set tax amount in transaction draft
+ useEffect(() => {
+ const taxAmount = getTaxAmount(transaction, taxRates.defaultValue);
+ const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount));
+
+ if (transaction.taxAmount && previousTransactionAmount === transaction.amount) {
+ return IOU.setMoneyRequestTaxAmount(transaction.transactionID, transaction.taxAmount, true);
+ }
+
+ IOU.setMoneyRequestTaxAmount(transaction.transactionID, amountInSmallestCurrencyUnits, true);
+ }, [taxRates.defaultValue, transaction, previousTransactionAmount]);
+
/**
* Returns the participants with amount
* @param {Array} participants
@@ -855,7 +874,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
key={`${taxRates.name}${formattedTaxAmount}`}
shouldShowRightIcon={!isReadOnly}
title={formattedTaxAmount}
- description={taxRates.name}
+ description={translate('iou.taxAmount')}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
onPress={() => Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))}
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 074baa586dba..c052843fefe2 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -23,6 +23,7 @@ import * as CardUtils from '@libs/CardUtils';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
+import {isTaxPolicyEnabled} from '@libs/PolicyUtils';
import * as ReceiptUtils from '@libs/ReceiptUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
@@ -100,6 +101,8 @@ function MoneyRequestView({
const {
created: transactionDate,
amount: transactionAmount,
+ taxAmount: transactionTaxAmount,
+ taxCode: transactionTaxCode,
currency: transactionCurrency,
comment: transactionDescription,
merchant: transactionMerchant,
@@ -119,6 +122,15 @@ function MoneyRequestView({
const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
const cardProgramName = isCardTransaction && transactionCardID !== undefined ? CardUtils.getCardDescription(transactionCardID) : '';
const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
+ const taxRates = policy?.taxRates;
+ const formattedTaxAmount = transactionTaxAmount ? CurrencyUtils.convertToDisplayString(transactionTaxAmount, transactionCurrency) : '';
+
+ const taxRatesDescription = taxRates?.name;
+ const taxRateTitle =
+ taxRates &&
+ (transactionTaxCode === taxRates?.defaultExternalID
+ ? transaction && TransactionUtils.getDefaultTaxName(taxRates, transaction)
+ : transactionTaxCode && TransactionUtils.getTaxName(taxRates?.taxes, transactionTaxCode));
// Flags for allowing or disallowing editing a money request
const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID);
@@ -147,6 +159,9 @@ function MoneyRequestView({
const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists));
const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true));
+ // A flag for showing tax rate
+ const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy) && transactionTaxCode && transactionTaxAmount;
+
const {getViolationsForField} = useViolations(transactionViolations ?? []);
const hasViolations = useCallback(
(field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0,
@@ -423,6 +438,31 @@ function MoneyRequestView({
/>
)}
+ {shouldShowTax && (
+
+ Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAX_RATE))}
+ />
+
+ )}
+
+ {shouldShowTax && (
+
+ Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAX_AMOUNT))}
+ />
+
+ )}
{shouldShowBillable && (
diff --git a/src/components/TaxPicker.tsx b/src/components/TaxPicker.tsx
index 936bd23b530d..4980025024ed 100644
--- a/src/components/TaxPicker.tsx
+++ b/src/components/TaxPicker.tsx
@@ -1,22 +1,31 @@
-import React, {useCallback, useMemo, useState} from 'react';
+import React, {useMemo, useState} from 'react';
+import {withOnyx} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
import type {EdgeInsets} from 'react-native-safe-area-context';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import CONST from '@src/CONST';
-import type {TaxRatesWithDefault} from '@src/types/onyx';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Policy} from '@src/types/onyx';
import SelectionList from './SelectionList';
import RadioListItem from './SelectionList/RadioListItem';
import type {ListItem} from './SelectionList/types';
-type TaxPickerProps = {
- /** Collection of tax rates attached to a policy */
- taxRates?: TaxRatesWithDefault;
+type TaxPickerOnyxProps = {
+ /** The policy which the user has access to and which the report is tied to */
+ policy: OnyxEntry;
+};
+type TaxPickerProps = TaxPickerOnyxProps & {
/** The selected tax rate of an expense */
selectedTaxRate?: string;
+ /** ID of the policy */
+ // eslint-disable-next-line react/no-unused-prop-types
+ policyID?: string;
+
/**
* Safe area insets required for reflecting the portion of the view,
* that is not covered by navigation bars, tab bars, toolbars, and other ancestor views.
@@ -27,18 +36,17 @@ type TaxPickerProps = {
onSubmit: (tax: ListItem) => void;
};
-function TaxPicker({selectedTaxRate = '', taxRates, insets, onSubmit}: TaxPickerProps) {
+function TaxPicker({selectedTaxRate = '', policy, insets, onSubmit}: TaxPickerProps) {
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const [searchValue, setSearchValue] = useState('');
+ const taxRates = policy?.taxRates;
const taxRatesCount = TransactionUtils.getEnabledTaxRateCount(taxRates?.taxes ?? {});
const isTaxRatesCountBelowThreshold = taxRatesCount < CONST.TAX_RATES_LIST_THRESHOLD;
const shouldShowTextInput = !isTaxRatesCountBelowThreshold;
- const getTaxName = useCallback((key: string) => taxRates?.taxes[key]?.name, [taxRates?.taxes]);
-
const selectedOptions = useMemo(() => {
if (!selectedTaxRate) {
return [];
@@ -46,36 +54,39 @@ function TaxPicker({selectedTaxRate = '', taxRates, insets, onSubmit}: TaxPicker
return [
{
- name: getTaxName(selectedTaxRate),
+ name: selectedTaxRate,
enabled: true,
accountID: null,
},
];
- }, [selectedTaxRate, getTaxName]);
+ }, [selectedTaxRate]);
- const sections = useMemo(
- () => OptionsListUtils.getTaxRatesSection(taxRates, selectedOptions as OptionsListUtils.Category[], searchValue, selectedTaxRate),
- [taxRates, searchValue, selectedOptions, selectedTaxRate],
- );
+ const sections = useMemo(() => OptionsListUtils.getTaxRatesSection(taxRates, selectedOptions as OptionsListUtils.Category[], searchValue), [taxRates, searchValue, selectedOptions]);
const headerMessage = OptionsListUtils.getHeaderMessageForNonUserList(sections[0].data.length > 0, searchValue);
+ const selectedOptionKey = useMemo(() => sections?.[0]?.data?.find((taxRate) => taxRate.searchText === selectedTaxRate)?.keyForList, [sections, selectedTaxRate]);
+
return (
);
}
TaxPicker.displayName = 'TaxPicker';
-export default TaxPicker;
+export default withOnyx({
+ policy: {
+ key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ },
+})(TaxPicker);
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index fd84e65c028e..9e6d4a6a5ecf 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -143,6 +143,8 @@ const WRITE_COMMANDS = {
UPDATE_MONEY_REQUEST_BILLABLE: 'UpdateMoneyRequestBillable',
UPDATE_MONEY_REQUEST_MERCHANT: 'UpdateMoneyRequestMerchant',
UPDATE_MONEY_REQUEST_TAG: 'UpdateMoneyRequestTag',
+ UPDATE_MONEY_REQUEST_TAX_AMOUNT: 'UpdateMoneyRequestTaxAmount',
+ UPDATE_MONEY_REQUEST_TAX_RATE: 'UpdateMoneyRequestTaxRate',
UPDATE_MONEY_REQUEST_DISTANCE: 'UpdateMoneyRequestDistance',
UPDATE_MONEY_REQUEST_CATEGORY: 'UpdateMoneyRequestCategory',
UPDATE_MONEY_REQUEST_DESCRIPTION: 'UpdateMoneyRequestDescription',
@@ -329,6 +331,8 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_MERCHANT]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_AMOUNT]: Parameters.UpdateMoneyRequestParams;
+ [WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_RATE]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DESCRIPTION]: Parameters.UpdateMoneyRequestParams;
diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts
index 8f1cb89d695b..0d961ea27115 100644
--- a/src/libs/ModifiedExpenseMessage.ts
+++ b/src/libs/ModifiedExpenseMessage.ts
@@ -55,6 +55,14 @@ function buildMessageFragmentForValue(
}
}
+/**
+ * Get the absolute value for a tax amount.
+ */
+function getTaxAmountAbsValue(taxAmount: number): number {
+ // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value
+ return Math.abs(taxAmount ?? 0);
+}
+
/**
* Get the message line for a modified expense.
*/
@@ -116,6 +124,7 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr
'currency' in reportActionOriginalMessage;
const hasModifiedMerchant = reportActionOriginalMessage && 'oldMerchant' in reportActionOriginalMessage && 'merchant' in reportActionOriginalMessage;
+
if (hasModifiedAmount) {
const oldCurrency = reportActionOriginalMessage?.oldCurrency ?? '';
const oldAmountValue = reportActionOriginalMessage?.oldAmount ?? 0;
@@ -216,6 +225,29 @@ function getForReportAction(reportID: string | undefined, reportAction: OnyxEntr
});
}
+ const hasModifiedTaxAmount = reportActionOriginalMessage && 'oldTaxAmount' in reportActionOriginalMessage && 'taxAmount' in reportActionOriginalMessage;
+ if (hasModifiedTaxAmount) {
+ const currency = reportActionOriginalMessage?.currency;
+
+ const taxAmount = CurrencyUtils.convertToDisplayString(getTaxAmountAbsValue(reportActionOriginalMessage?.taxAmount ?? 0), currency);
+ const oldTaxAmountValue = getTaxAmountAbsValue(reportActionOriginalMessage?.oldTaxAmount ?? 0);
+ const oldTaxAmount = oldTaxAmountValue > 0 ? CurrencyUtils.convertToDisplayString(oldTaxAmountValue, currency) : '';
+ buildMessageFragmentForValue(taxAmount, oldTaxAmount, Localize.translateLocal('iou.taxAmount'), false, setFragments, removalFragments, changeFragments);
+ }
+
+ const hasModifiedTaxRate = reportActionOriginalMessage && 'oldTaxRate' in reportActionOriginalMessage && 'taxRate' in reportActionOriginalMessage;
+ if (hasModifiedTaxRate) {
+ buildMessageFragmentForValue(
+ reportActionOriginalMessage?.taxRate ?? '',
+ reportActionOriginalMessage?.oldTaxRate ?? '',
+ Localize.translateLocal('iou.taxRate'),
+ false,
+ setFragments,
+ removalFragments,
+ changeFragments,
+ );
+ }
+
const hasModifiedBillable = reportActionOriginalMessage && 'oldBillable' in reportActionOriginalMessage && 'billable' in reportActionOriginalMessage;
if (hasModifiedBillable) {
buildMessageFragmentForValue(
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 7e4082bff481..ca44931e7e8e 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -1191,8 +1191,8 @@ function hasEnabledTags(policyTagList: Array
* @param taxRates - The original tax rates object.
* @returns The transformed tax rates object.g
*/
-function transformedTaxRates(taxRates: TaxRatesWithDefault | undefined, defaultKey?: string): Record {
- const defaultTaxKey = defaultKey ?? taxRates?.defaultExternalID;
+function transformedTaxRates(taxRates: TaxRatesWithDefault | undefined): Record {
+ const defaultTaxKey = taxRates?.defaultExternalID;
const getModifiedName = (data: TaxRate, code: string) => `${data.name} (${data.value})${defaultTaxKey === code ? ` • ${Localize.translateLocal('common.default')}` : ''}`;
const taxes = Object.fromEntries(Object.entries(taxRates?.taxes ?? {}).map(([code, data]) => [code, {...data, code, modifiedName: getModifiedName(data, code), name: data.name}]));
return taxes;
@@ -1212,7 +1212,7 @@ function sortTaxRates(taxRates: TaxRates): TaxRate[] {
function getTaxRatesOptions(taxRates: Array>): Option[] {
return taxRates.map((taxRate) => ({
text: taxRate.modifiedName,
- keyForList: taxRate.code,
+ keyForList: taxRate.modifiedName,
searchText: taxRate.modifiedName,
tooltipText: taxRate.modifiedName,
isDisabled: taxRate.isDisabled,
@@ -1223,10 +1223,10 @@ function getTaxRatesOptions(taxRates: Array>): Option[] {
/**
* Builds the section list for tax rates
*/
-function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedOptions: Category[], searchInputValue: string, defaultTaxKey?: string): CategorySection[] {
+function getTaxRatesSection(taxRates: TaxRatesWithDefault | undefined, selectedOptions: Category[], searchInputValue: string): CategorySection[] {
const policyRatesSections = [];
- const taxes = transformedTaxRates(taxRates, defaultTaxKey);
+ const taxes = transformedTaxRates(taxRates);
const sortedTaxRates = sortTaxRates(taxes);
const enabledTaxRates = sortedTaxRates.filter((taxRate) => !taxRate.isDisabled);
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 0a8437a5afaf..d393f8e64fba 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -244,6 +244,10 @@ function isPaidGroupPolicy(policy: OnyxEntry | EmptyObject): boolean {
return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE;
}
+function isTaxPolicyEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry): boolean {
+ return (isPolicyExpenseChat && (policy?.tax?.trackingEnabled ?? policy?.isTaxTrackingEnabled)) ?? false;
+}
+
/**
* Checks if policy's scheduled submit / auto reporting frequency is "instant".
* Note: Free policies have "instant" submit always enabled.
@@ -328,6 +332,7 @@ export {
isInstantSubmitEnabled,
isFreeGroupPolicy,
isPolicyAdmin,
+ isTaxPolicyEnabled,
isSubmitAndClose,
getMemberAccountIDsForWorkspace,
getIneligibleInvitees,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index a2bf892b96b4..8296e38411be 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -99,6 +99,10 @@ type ExpenseOriginalMessage = {
oldTag?: string;
billable?: string;
oldBillable?: string;
+ oldTaxAmount?: number;
+ taxAmount?: number;
+ taxRate?: string;
+ oldTaxRate?: string;
};
type SpendBreakdown = {
@@ -327,6 +331,8 @@ type OptimisticTaskReport = Pick<
type TransactionDetails = {
created: string;
amount: number;
+ taxAmount?: number;
+ taxCode?: string;
currency: string;
merchant: string;
waypoints?: WaypointCollection | string;
@@ -2290,6 +2296,8 @@ function getTransactionDetails(transaction: OnyxEntry, createdDateF
return {
created: TransactionUtils.getCreated(transaction, createdDateFormat),
amount: TransactionUtils.getAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)),
+ taxAmount: TransactionUtils.getTaxAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)),
+ taxCode: TransactionUtils.getTaxCode(transaction),
currency: TransactionUtils.getCurrency(transaction),
comment: TransactionUtils.getDescription(transaction),
merchant: TransactionUtils.getMerchant(transaction),
@@ -2714,7 +2722,12 @@ function getReportPreviewMessage(
*
* At the moment, we only allow changing one transaction field at a time.
*/
-function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry, transactionChanges: TransactionChanges, isFromExpenseReport: boolean): ExpenseOriginalMessage {
+function getModifiedExpenseOriginalMessage(
+ oldTransaction: OnyxEntry,
+ transactionChanges: TransactionChanges,
+ isFromExpenseReport: boolean,
+ policy: OnyxEntry,
+): ExpenseOriginalMessage {
const originalMessage: ExpenseOriginalMessage = {};
// Remark: Comment field is the only one which has new/old prefixes for the keys (newComment/ oldComment),
// all others have old/- pattern such as oldCreated/created
@@ -2750,6 +2763,16 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry,
transactionChanges: TransactionChanges,
isFromExpenseReport: boolean,
+ policy: OnyxEntry,
): OptimisticModifiedExpenseReportAction {
- const originalMessage = getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, isFromExpenseReport);
+ const originalMessage = getModifiedExpenseOriginalMessage(oldTransaction, transactionChanges, isFromExpenseReport, policy);
return {
actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE,
actorAccountID: currentUserAccountID,
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index cdd23aac1596..430100e84b2f 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -4,11 +4,12 @@ import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {RecentWaypoint, Report, TaxRate, TaxRates, Transaction, TransactionViolation} from '@src/types/onyx';
+import type {RecentWaypoint, Report, TaxRate, TaxRates, TaxRatesWithDefault, Transaction, TransactionViolation} from '@src/types/onyx';
import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {isCorporateCard, isExpensifyCard} from './CardUtils';
import DateUtils from './DateUtils';
+import * as Localize from './Localize';
import * as NumberUtils from './NumberUtils';
import {getCleanedTagName} from './PolicyUtils';
@@ -207,6 +208,16 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra
shouldStopSmartscan = true;
}
+ if (Object.hasOwn(transactionChanges, 'taxAmount') && typeof transactionChanges.taxAmount === 'number') {
+ updatedTransaction.taxAmount = isFromExpenseReport ? -transactionChanges.taxAmount : transactionChanges.taxAmount;
+ shouldStopSmartscan = true;
+ }
+
+ if (Object.hasOwn(transactionChanges, 'taxCode') && typeof transactionChanges.taxCode === 'string') {
+ updatedTransaction.taxCode = transactionChanges.taxCode;
+ shouldStopSmartscan = true;
+ }
+
if (Object.hasOwn(transactionChanges, 'billable') && typeof transactionChanges.billable === 'boolean') {
updatedTransaction.billable = transactionChanges.billable;
}
@@ -240,6 +251,8 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra
...(Object.hasOwn(transactionChanges, 'billable') && {billable: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(Object.hasOwn(transactionChanges, 'category') && {category: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
...(Object.hasOwn(transactionChanges, 'tag') && {tag: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
+ ...(Object.hasOwn(transactionChanges, 'taxAmount') && {taxAmount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
+ ...(Object.hasOwn(transactionChanges, 'taxCode') && {taxCode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
};
return updatedTransaction;
@@ -280,6 +293,27 @@ function getAmount(transaction: OnyxEntry, isFromExpenseReport = fa
return amount ? -amount : 0;
}
+/**
+ * Return the tax amount field from the transaction.
+ */
+function getTaxAmount(transaction: OnyxEntry, isFromExpenseReport: boolean): number {
+ // IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value
+ if (!isFromExpenseReport) {
+ return Math.abs(transaction?.taxAmount ?? 0);
+ }
+
+ // To avoid -0 being shown, lets only change the sign if the value is other than 0.
+ const amount = transaction?.taxAmount ?? 0;
+ return amount ? -amount : 0;
+}
+
+/**
+ * Return the tax code from the transaction.
+ */
+function getTaxCode(transaction: OnyxEntry): string {
+ return transaction?.taxCode ?? '';
+}
+
/**
* Return the currency field from the transaction, return the modifiedCurrency if present.
*/
@@ -586,9 +620,29 @@ function getEnabledTaxRateCount(options: TaxRates) {
return Object.values(options).filter((option: TaxRate) => !option.isDisabled).length;
}
+/**
+ * Gets the default tax name
+ */
+function getDefaultTaxName(taxRates: TaxRatesWithDefault, transaction: Transaction) {
+ const defaultTaxKey = taxRates.defaultExternalID;
+ const defaultTaxName = (defaultTaxKey && `${taxRates.taxes[defaultTaxKey].name} (${taxRates.taxes[defaultTaxKey].value}) • ${Localize.translateLocal('common.default')}`) || '';
+ return transaction?.taxRate?.text ?? defaultTaxName;
+}
+
+/**
+ * Gets the tax name
+ */
+function getTaxName(taxes: TaxRates, transactionTaxCode: string) {
+ const taxName = `${taxes[transactionTaxCode].name}`;
+ const taxValue = `${taxes[transactionTaxCode].value}`;
+ return transactionTaxCode ? `${taxName} (${taxValue})` : '';
+}
+
export {
buildOptimisticTransaction,
calculateTaxAmount,
+ getTaxName,
+ getDefaultTaxName,
getEnabledTaxRateCount,
getUpdatedTransaction,
getDescription,
@@ -597,6 +651,8 @@ export {
isManualRequest,
isScanRequest,
getAmount,
+ getTaxAmount,
+ getTaxCode,
getCurrency,
getDistance,
getCardID,
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index 92e05996fe3d..761ae02ea4c6 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -1533,7 +1533,7 @@ function getUpdateMoneyRequestParams(
// We don't create a modified report action if we're updating the waypoints,
// since there isn't actually any optimistic data we can create for them and the report action is created on the server
// with the response from the MapBox API
- const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport);
+ const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport, policy);
if (!hasPendingWaypoints) {
params.reportActionID = updatedReportAction.reportActionID;
@@ -1743,6 +1743,7 @@ function getUpdateTrackExpenseParams(
transactionThreadReportID: string,
transactionChanges: TransactionChanges,
onlyIncludeChangedFields: boolean,
+ policy: OnyxEntry,
): UpdateMoneyRequestData {
const optimisticData: OnyxUpdate[] = [];
const successData: OnyxUpdate[] = [];
@@ -1809,7 +1810,7 @@ function getUpdateTrackExpenseParams(
// We don't create a modified report action if we're updating the waypoints,
// since there isn't actually any optimistic data we can create for them and the report action is created on the server
// with the response from the MapBox API
- const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, false);
+ const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, false, policy);
if (!hasPendingWaypoints) {
params.reportActionID = updatedReportAction.reportActionID;
@@ -1922,7 +1923,7 @@ function updateMoneyRequestDate(
const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null;
let data: UpdateMoneyRequestData;
if (ReportUtils.isTrackExpenseReport(transactionThreadReport)) {
- data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true);
+ data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true, policy);
} else {
data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTags, policyCategories, true);
}
@@ -1961,7 +1962,7 @@ function updateMoneyRequestMerchant(
const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null;
let data: UpdateMoneyRequestData;
if (ReportUtils.isTrackExpenseReport(transactionThreadReport)) {
- data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true);
+ data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true, policy);
} else {
data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true);
}
@@ -1985,6 +1986,38 @@ function updateMoneyRequestTag(
API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG, params, onyxData);
}
+/** Updates the created tax amount of a money request */
+function updateMoneyRequestTaxAmount(
+ transactionID: string,
+ optimisticReportActionID: string,
+ taxAmount: number,
+ policy: OnyxEntry,
+ policyTagList: OnyxEntry,
+ policyCategories: OnyxEntry,
+) {
+ const transactionChanges = {
+ taxAmount,
+ };
+ const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, policy, policyTagList, policyCategories, true);
+ API.write('UpdateMoneyRequestTaxAmount', params, onyxData);
+}
+
+/** Updates the created tax rate of a money request */
+function updateMoneyRequestTaxRate(
+ transactionID: string,
+ optimisticReportActionID: string,
+ taxCode: string,
+ policy: OnyxEntry,
+ policyTagList: OnyxEntry,
+ policyCategories: OnyxEntry,
+) {
+ const transactionChanges = {
+ taxCode,
+ };
+ const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, optimisticReportActionID, transactionChanges, policy, policyTagList, policyCategories, true);
+ API.write('UpdateMoneyRequestTaxRate', params, onyxData);
+}
+
/** Updates the waypoints of a distance money request */
function updateMoneyRequestDistance(
transactionID: string,
@@ -2000,7 +2033,7 @@ function updateMoneyRequestDistance(
const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null;
let data: UpdateMoneyRequestData;
if (ReportUtils.isTrackExpenseReport(transactionThreadReport)) {
- data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true);
+ data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true, policy);
} else {
data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true);
}
@@ -2039,7 +2072,7 @@ function updateMoneyRequestDescription(
const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null;
let data: UpdateMoneyRequestData;
if (ReportUtils.isTrackExpenseReport(transactionThreadReport)) {
- data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true);
+ data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true, policy);
} else {
data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories, true);
}
@@ -3330,7 +3363,7 @@ function editRegularMoneyRequest(
const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport);
// STEP 2: Build new modified expense report action.
- const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport);
+ const updatedReportAction = ReportUtils.buildOptimisticModifiedExpenseReportAction(transactionThread, transaction, transactionChanges, isFromExpenseReport, policy);
const updatedTransaction = transaction ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) : null;
// STEP 3: Compute the IOU total and update the report preview message so LHN amount owed is correct
@@ -5067,8 +5100,8 @@ function setMoneyRequestTaxRate(transactionID: string, taxRate: TaxRate) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxRate});
}
-function setMoneyRequestTaxAmount(transactionID: string, taxAmount: number) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxAmount});
+function setMoneyRequestTaxAmount(transactionID: string, taxAmount: number, isDraft: boolean) {
+ Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {taxAmount});
}
function setMoneyRequestBillable(billable: boolean) {
@@ -5280,6 +5313,8 @@ export {
updateMoneyRequestBillable,
updateMoneyRequestMerchant,
updateMoneyRequestTag,
+ updateMoneyRequestTaxAmount,
+ updateMoneyRequestTaxRate,
updateMoneyRequestDistance,
updateMoneyRequestCategory,
updateMoneyRequestAmountAndCurrency,
diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js
index d3941dca044e..ff2c2c5ce6ea 100644
--- a/src/pages/EditRequestPage.js
+++ b/src/pages/EditRequestPage.js
@@ -8,10 +8,12 @@ import ScreenWrapper from '@components/ScreenWrapper';
import tagPropTypes from '@components/tagPropTypes';
import transactionPropTypes from '@components/transactionPropTypes';
import compose from '@libs/compose';
+import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as IOUUtils from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
+import {isTaxPolicyEnabled} from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import * as IOU from '@userActions/IOU';
@@ -19,6 +21,8 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import EditRequestReceiptPage from './EditRequestReceiptPage';
import EditRequestTagPage from './EditRequestTagPage';
+import EditRequestTaxAmountPage from './EditRequestTaxAmountPage';
+import EditRequestTaxRatePage from './EditRequestTaxRatePage';
import reportActionPropTypes from './home/report/reportActionPropTypes';
import reportPropTypes from './reportPropTypes';
import {policyPropTypes} from './workspace/withPolicy';
@@ -68,10 +72,17 @@ const defaultProps = {
transaction: {},
};
+const getTaxAmount = (transactionAmount, transactionTaxCode, taxRates) => {
+ const percentage = (transactionTaxCode ? taxRates.taxes[transactionTaxCode].value : taxRates.defaultValue) || '';
+ return CurrencyUtils.convertToBackendAmount(Number.parseFloat(TransactionUtils.calculateTaxAmount(percentage, transactionAmount)));
+};
+
function EditRequestPage({report, route, policy, policyCategories, policyTags, parentReportActions, transaction}) {
const parentReportActionID = lodashGet(report, 'parentReportActionID', '0');
const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {});
- const {tag: transactionTag} = ReportUtils.getTransactionDetails(transaction);
+ const {taxAmount: transactionTaxAmount, taxCode: transactionTaxCode, currency: transactionCurrency, tag: transactionTag} = ReportUtils.getTransactionDetails(transaction);
+
+ const defaultCurrency = lodashGet(route, 'params.currency', '') || transactionCurrency;
const fieldToEdit = lodashGet(route, ['params', 'field'], '');
const tagListIndex = Number(lodashGet(route, ['params', 'tagIndex'], undefined));
@@ -80,12 +91,23 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p
const policyTagListName = PolicyUtils.getTagListName(policyTags, tagListIndex);
const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]);
+ const taxRates = lodashGet(policy, 'taxRates', {});
+
+ const taxRateTitle =
+ taxRates &&
+ (transactionTaxCode === taxRates.defaultExternalID
+ ? transaction && TransactionUtils.getDefaultTaxName(taxRates, transaction)
+ : transactionTaxCode && TransactionUtils.getTaxName(taxRates.taxes, transactionTaxCode));
+
// A flag for verifying that the current report is a sub-report of a workspace chat
const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report);
// A flag for showing the tags page
const shouldShowTags = useMemo(() => isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)), [isPolicyExpenseChat, policyTagLists, transactionTag]);
+ // A flag for showing tax rate
+ const shouldShowTax = isTaxPolicyEnabled(isPolicyExpenseChat, policy);
+
// Decides whether to allow or disallow editing a money request
useEffect(() => {
// Do not dismiss the modal, when a current user can edit this property of the money request.
@@ -99,6 +121,35 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p
});
}, [parentReportAction, fieldToEdit]);
+ const updateTaxAmount = useCallback(
+ (transactionChanges) => {
+ const newTaxAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(transactionChanges.amount));
+
+ if (newTaxAmount === TransactionUtils.getTaxAmount(transaction)) {
+ Navigation.dismissModal();
+ return;
+ }
+ IOU.updateMoneyRequestTaxAmount(transaction.transactionID, report.reportID, newTaxAmount, policy, policyTags, policyCategories);
+ Navigation.dismissModal(report.reportID);
+ },
+ [transaction, report, policy, policyTags, policyCategories],
+ );
+
+ const updateTaxRate = useCallback(
+ (transactionChanges) => {
+ const newTaxCode = transactionChanges.data.code;
+
+ if (newTaxCode === undefined || newTaxCode === TransactionUtils.getTaxCode(transaction)) {
+ Navigation.dismissModal();
+ return;
+ }
+
+ IOU.updateMoneyRequestTaxRate(transaction.transactionID, report.reportID, newTaxCode, policy, policyTags, policyCategories);
+ Navigation.dismissModal(report.reportID);
+ },
+ [transaction, report, policy, policyTags, policyCategories],
+ );
+
const saveTag = useCallback(
({tag: newTag}) => {
let updatedTag = newTag;
@@ -131,6 +182,27 @@ function EditRequestPage({report, route, policy, policyCategories, policyTags, p
);
}
+ if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAX_AMOUNT && shouldShowTax) {
+ return (
+
+ );
+ }
+
+ if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.TAX_RATE && shouldShowTax) {
+ return (
+
+ );
+ }
+
if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) {
return (
void;
+};
+
+function EditRequestTaxAmountPage({defaultAmount, defaultTaxAmount, defaultCurrency, onSubmit}: EditRequestTaxAmountPageProps) {
+ const {translate} = useLocalize();
+ const textInput = useRef(null);
+
+ const focusTimeoutRef = useRef(null);
+
+ useFocusEffect(
+ useCallback(() => {
+ focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION);
+ return () => {
+ if (!focusTimeoutRef.current) {
+ return;
+ }
+ clearTimeout(focusTimeoutRef.current);
+ };
+ }, []),
+ );
+
+ return (
+
+
+
+
+ );
+}
+
+EditRequestTaxAmountPage.displayName = 'EditRequestTaxAmountPage';
+
+export default EditRequestTaxAmountPage;
diff --git a/src/pages/EditRequestTaxRatePage.tsx b/src/pages/EditRequestTaxRatePage.tsx
new file mode 100644
index 000000000000..099851e92209
--- /dev/null
+++ b/src/pages/EditRequestTaxRatePage.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import TaxPicker from '@components/TaxPicker';
+import useLocalize from '@hooks/useLocalize';
+
+type EditRequestTaxRatePageProps = {
+ /** Transaction default tax Rate value */
+ defaultTaxRate: string;
+
+ /** The policyID we are getting categories for */
+ policyID: string;
+
+ /** Callback to fire when the Save button is pressed */
+ onSubmit: () => void;
+};
+
+function EditRequestTaxRatePage({defaultTaxRate, policyID, onSubmit}: EditRequestTaxRatePageProps) {
+ const {translate} = useLocalize();
+
+ return (
+
+ {({insets}) => (
+ <>
+
+
+ >
+ )}
+
+ );
+}
+
+EditRequestTaxRatePage.displayName = 'EditRequestTaxRatePage';
+
+export default EditRequestTaxRatePage;
diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js
index 9465c7e3edae..1c3cdefd4392 100644
--- a/src/pages/iou/request/step/IOURequestStepAmount.js
+++ b/src/pages/iou/request/step/IOURequestStepAmount.js
@@ -1,10 +1,8 @@
import {useFocusEffect} from '@react-navigation/native';
import lodashGet from 'lodash/get';
import lodashIsEmpty from 'lodash/isEmpty';
-import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useRef} from 'react';
import {withOnyx} from 'react-native-onyx';
-import taxPropTypes from '@components/taxPropTypes';
import transactionPropTypes from '@components/transactionPropTypes';
import useLocalize from '@hooks/useLocalize';
import * as TransactionEdit from '@libs/actions/TransactionEdit';
@@ -41,24 +39,6 @@ const propTypes = {
/** The draft transaction object being modified in Onyx */
draftTransaction: transactionPropTypes,
-
- /** The policy of the report */
- policy: PropTypes.shape({
- /**
- * Whether or not the policy has tax tracking enabled
- *
- * @deprecated - use tax.trackingEnabled instead
- */
- isTaxTrackingEnabled: PropTypes.bool,
-
- /** Whether or not the policy has tax tracking enabled */
- tax: PropTypes.shape({
- trackingEnabled: PropTypes.bool,
- }),
-
- /** Collection of tax rates attached to a policy */
- taxRates: taxPropTypes,
- }),
};
const defaultProps = {
@@ -66,12 +46,6 @@ const defaultProps = {
transaction: {},
splitDraftTransaction: {},
draftTransaction: {},
- policy: {},
-};
-
-const getTaxAmount = (transaction, defaultTaxValue, amount) => {
- const percentage = (transaction.taxRate ? transaction.taxRate.data.value : defaultTaxValue) || '';
- return TransactionUtils.calculateTaxAmount(percentage, amount);
};
function IOURequestStepAmount({
@@ -82,7 +56,6 @@ function IOURequestStepAmount({
transaction,
splitDraftTransaction,
draftTransaction,
- policy,
}) {
const {translate} = useLocalize();
const textInput = useRef(null);
@@ -96,10 +69,6 @@ function IOURequestStepAmount({
const {amount: transactionAmount} = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction);
const {currency} = ReportUtils.getTransactionDetails(isEditing ? draftTransaction : transaction);
- const taxRates = lodashGet(policy, 'taxRates', {});
- const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report));
- const isTaxTrackingEnabled = isPolicyExpenseChat && lodashGet(policy, 'tax.trackingEnabled', policy.isTaxTrackingEnabled);
-
useFocusEffect(
useCallback(() => {
focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION);
@@ -156,12 +125,6 @@ function IOURequestStepAmount({
isSaveButtonPressed.current = true;
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount));
- if ((iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL || backTo) && isTaxTrackingEnabled) {
- const taxAmount = getTaxAmount(transaction, taxRates.defaultValue, amountInSmallestCurrencyUnits);
- const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount));
- IOU.setMoneyRequestTaxAmount(transaction.transactionID, taxAmountInSmallestCurrencyUnits);
- }
-
IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true);
if (backTo) {
@@ -242,9 +205,6 @@ export default compose(
return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`;
},
},
- policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
- },
draftTransaction: {
key: ({route}) => {
const transactionID = lodashGet(route, 'params.transactionID', 0);
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js
index 61ae0d2b67b7..0df9a7333e7a 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.js
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js
@@ -90,7 +90,8 @@ function IOURequestStepConfirmation({
const receiptFilename = lodashGet(transaction, 'filename');
const receiptPath = lodashGet(transaction, 'receipt.source');
const receiptType = lodashGet(transaction, 'receipt.type');
- const transactionTaxCode = transaction.taxRate && transaction.taxRate.keyForList;
+ const foreignTaxDefault = lodashGet(policy, 'taxRates.foreignTaxDefault');
+ const transactionTaxCode = transaction.taxRate ? transaction.taxRate.data.code : foreignTaxDefault;
const transactionTaxAmount = transaction.taxAmount;
const requestType = TransactionUtils.getRequestType(transaction);
const headerTitle = useMemo(() => {
diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
index fb984cb801c1..3d5ddcc1a47f 100644
--- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
+++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
@@ -2,18 +2,12 @@ import {useFocusEffect} from '@react-navigation/native';
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useCallback, useEffect, useRef} from 'react';
-import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
import taxPropTypes from '@components/taxPropTypes';
import transactionPropTypes from '@components/transactionPropTypes';
import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import * as CurrencyUtils from '@libs/CurrencyUtils';
-import * as IOUUtils from '@libs/IOUUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as TransactionUtils from '@libs/TransactionUtils';
import MoneyRequestAmountForm from '@pages/iou/steps/MoneyRequestAmountForm';
@@ -23,6 +17,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes';
+import StepScreenWrapper from './StepScreenWrapper';
import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import withWritableReportOrNotFound from './withWritableReportOrNotFound';
@@ -47,8 +42,8 @@ const propTypes = {
const defaultProps = {
report: {},
- transaction: {},
policy: {},
+ transaction: {},
};
const getTaxAmount = (transaction, defaultTaxValue) => {
@@ -66,7 +61,6 @@ function IOURequestStepTaxAmountPage({
policy,
}) {
const {translate} = useLocalize();
- const styles = useThemeStyles();
const textInput = useRef(null);
const isEditing = Navigation.getActiveRoute().includes('taxAmount');
@@ -120,7 +114,7 @@ function IOURequestStepTaxAmountPage({
const updateTaxAmount = (currentAmount) => {
isSaveButtonPressed.current = true;
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount.amount));
- IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits);
+ IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true);
IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD, true);
@@ -144,37 +138,24 @@ function IOURequestStepTaxAmountPage({
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(iouType, transactionID, reportID));
};
- const content = (
- (textInput.current = e)}
- onCurrencyButtonPress={navigateToCurrencySelectionPage}
- onSubmitButtonPress={updateTaxAmount}
- />
- );
-
return (
-
- {({safeAreaPaddingBottomStyle}) => (
-
-
-
- {content}
-
-
- )}
-
+ (textInput.current = e)}
+ onCurrencyButtonPress={navigateToCurrencySelectionPage}
+ onSubmitButtonPress={updateTaxAmount}
+ />
+
);
}
diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js
index 335964adf309..d4a2c10d24b0 100644
--- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.js
+++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.js
@@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
import TaxPicker from '@components/TaxPicker';
import taxPropTypes from '@components/taxPropTypes';
import transactionPropTypes from '@components/transactionPropTypes';
@@ -14,9 +12,11 @@ import * as CurrencyUtils from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
+import reportPropTypes from '@pages/reportPropTypes';
import * as IOU from '@userActions/IOU';
import ONYXKEYS from '@src/ONYXKEYS';
import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes';
+import StepScreenWrapper from './StepScreenWrapper';
import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import withWritableReportOrNotFound from './withWritableReportOrNotFound';
@@ -27,6 +27,9 @@ const propTypes = {
/** The transaction object being modified in Onyx */
transaction: transactionPropTypes,
+ /** The report attached to the transaction */
+ report: reportPropTypes,
+
/* Onyx Props */
/** The policy of the report */
policy: PropTypes.shape({
@@ -36,6 +39,7 @@ const propTypes = {
};
const defaultProps = {
+ report: {},
policy: {},
transaction: {},
};
@@ -51,46 +55,40 @@ function IOURequestStepTaxRatePage({
},
policy,
transaction,
+ report,
}) {
const {translate} = useLocalize();
+ const taxRates = lodashGet(policy, 'taxRates', {});
+
const navigateBack = () => {
Navigation.goBack(backTo);
};
- const taxRates = lodashGet(policy, 'taxRates', {});
- const defaultTaxKey = taxRates.defaultExternalID;
- const selectedTaxRate = (transaction.taxRate && transaction.taxRate.keyForList) || defaultTaxKey;
+
+ const selectedTaxRate = TransactionUtils.getDefaultTaxName(taxRates, transaction);
const updateTaxRates = (taxes) => {
const taxAmount = getTaxAmount(taxRates, taxes.text, transaction.amount);
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount));
IOU.setMoneyRequestTaxRate(transaction.transactionID, taxes);
- IOU.setMoneyRequestTaxAmount(transaction.transactionID, amountInSmallestCurrencyUnits);
+ IOU.setMoneyRequestTaxAmount(transaction.transactionID, amountInSmallestCurrencyUnits, true);
Navigation.goBack(backTo);
};
return (
-
- {({insets}) => (
- <>
- navigateBack()}
- />
-
- >
- )}
-
+
+
);
}
diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx
index a010e13ff496..00970455fb8a 100644
--- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx
+++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx
@@ -34,8 +34,11 @@ type MoneyRequestAmountFormProps = {
/** Whether the amount is being edited or not */
isEditing?: boolean;
+ /** Whether the currency symbol is pressable */
+ isCurrencyPressable?: boolean;
+
/** Fired when back button pressed, navigates to currency selection page */
- onCurrencyButtonPress: () => void;
+ onCurrencyButtonPress?: () => void;
/** Fired when submit button pressed, saves the given amount and navigates to the next page */
onSubmitButtonPress: ({amount, currency}: {amount: string; currency: string}) => void;
@@ -59,7 +62,7 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength:
const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01;
const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) =>
- isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(taxAmount);
+ isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(Math.abs(taxAmount));
const AMOUNT_VIEW_ID = 'amountView';
const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView';
@@ -70,6 +73,7 @@ function MoneyRequestAmountForm(
amount = 0,
taxAmount = 0,
currency = CONST.CURRENCY.USD,
+ isCurrencyPressable = true,
isEditing = false,
onCurrencyButtonPress,
onSubmitButtonPress,
@@ -98,7 +102,7 @@ function MoneyRequestAmountForm(
const forwardDeletePressedRef = useRef(false);
- const formattedTaxAmount = CurrencyUtils.convertToDisplayString(taxAmount, currency);
+ const formattedTaxAmount = CurrencyUtils.convertToDisplayString(Math.abs(taxAmount), currency);
/**
* Event occurs when a user presses a mouse button over an DOM element.
@@ -301,7 +305,7 @@ function MoneyRequestAmountForm(
setSelection({start, end});
}}
onKeyPress={textInputKeyPress}
- isCurrencyPressable
+ isCurrencyPressable={isCurrencyPressable}
/>
{!!formError && (
{
setForeignCurrencyDefault(policyID, keyForList ?? '');
Navigation.goBack(ROUTES.WORKSPACE_TAXES_SETTINGS.getRoute(policyID));
@@ -55,8 +58,7 @@ function WorkspaceTaxesSettingsForeignCurrency({
diff --git a/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx b/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx
index 2fe2985daa22..c6de23069837 100644
--- a/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx
+++ b/src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx
@@ -10,6 +10,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {setWorkspaceCurrencyDefault} from '@libs/actions/Policy';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import * as TransactionUtils from '@libs/TransactionUtils';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import FeatureEnabledAccessOrNotFoundWrapper from '@pages/workspace/FeatureEnabledAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
@@ -31,6 +32,7 @@ function WorkspaceTaxesSettingsWorkspaceCurrency({
const {translate} = useLocalize();
const styles = useThemeStyles();
+ const selectedTaxRate = TransactionUtils.getTaxName(policy?.taxRates?.taxes ?? {}, policy?.taxRates?.foreignTaxDefault ?? '');
const submit = ({keyForList}: ListItem) => {
setWorkspaceCurrencyDefault(policyID, keyForList ?? '');
Navigation.goBack(ROUTES.WORKSPACE_TAXES_SETTINGS.getRoute(policyID));
@@ -55,8 +57,7 @@ function WorkspaceTaxesSettingsWorkspaceCurrency({
diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts
index 196267dc28cc..2e24fe00539a 100644
--- a/src/types/onyx/OriginalMessage.ts
+++ b/src/types/onyx/OriginalMessage.ts
@@ -268,6 +268,10 @@ type OriginalMessageModifiedExpense = {
category?: string;
oldTag?: string;
tag?: string;
+ oldTaxAmount?: number;
+ taxAmount?: number;
+ oldTaxRate?: string;
+ taxRate?: string;
oldBillable?: string;
billable?: string;
};
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index ddb0c33c2f0c..247eb64f48e9 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -39,7 +39,7 @@ type TaxRate = OnyxCommon.OnyxValueWithOfflineFeedback<{
/** Name of the a tax rate. */
name: string;
- /** The value of the tax rate as percentage. */
+ /** The value of the tax rate. */
value: string;
/** The code associated with the tax rate. If a tax is created in old dot, code field is undefined */
diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts
index 4c1d154fc1ad..1750fa61e514 100644
--- a/src/types/onyx/Transaction.ts
+++ b/src/types/onyx/Transaction.ts
@@ -102,6 +102,12 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback<
/** The original transaction amount */
amount: number;
+ /** The transaction tax amount */
+ taxAmount?: number;
+
+ /** The transaction tax code */
+ taxCode?: string;
+
/** Whether the request is billable */
billable?: boolean;
@@ -177,9 +183,6 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback<
/** The transaction tax rate */
taxRate?: TaxRate;
- /** Tax amount */
- taxAmount?: number;
-
/** Card Transactions */
/** The parent transaction id */
diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js
index d89c81f58262..49ad848fe466 100644
--- a/tests/unit/OptionsListUtilsTest.js
+++ b/tests/unit/OptionsListUtilsTest.js
@@ -2301,7 +2301,7 @@ describe('OptionsListUtils', () => {
// Adds 'Default' title to default tax.
// Adds value to tax name for more description.
text: 'Tax exempt 1 (0%) • Default',
- keyForList: 'CODE1',
+ keyForList: 'Tax exempt 1 (0%) • Default',
searchText: 'Tax exempt 1 (0%) • Default',
tooltipText: 'Tax exempt 1 (0%) • Default',
isDisabled: undefined,
@@ -2315,7 +2315,7 @@ describe('OptionsListUtils', () => {
},
{
text: 'Tax option 3 (5%)',
- keyForList: 'CODE3',
+ keyForList: 'Tax option 3 (5%)',
searchText: 'Tax option 3 (5%)',
tooltipText: 'Tax option 3 (5%)',
isDisabled: undefined,
@@ -2328,7 +2328,7 @@ describe('OptionsListUtils', () => {
},
{
text: 'Tax rate 2 (3%)',
- keyForList: 'CODE2',
+ keyForList: 'Tax rate 2 (3%)',
searchText: 'Tax rate 2 (3%)',
tooltipText: 'Tax rate 2 (3%)',
isDisabled: undefined,
@@ -2351,7 +2351,7 @@ describe('OptionsListUtils', () => {
data: [
{
text: 'Tax rate 2 (3%)',
- keyForList: 'CODE2',
+ keyForList: 'Tax rate 2 (3%)',
searchText: 'Tax rate 2 (3%)',
tooltipText: 'Tax rate 2 (3%)',
isDisabled: undefined,