diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index 3b98cf83f3cc..ba4f5f522c23 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -6715,6 +6715,7 @@ const CONST = {
CARD: this.TABLE_COLUMNS.CARD,
CATEGORY: this.TABLE_COLUMNS.CATEGORY,
TAG: this.TABLE_COLUMNS.TAG,
+ EXCHANGE_RATE: this.TABLE_COLUMNS.EXCHANGE_RATE,
ORIGINAL_AMOUNT: this.TABLE_COLUMNS.ORIGINAL_AMOUNT,
REPORT_ID: this.TABLE_COLUMNS.REPORT_ID,
BASE_62_REPORT_ID: this.TABLE_COLUMNS.BASE_62_REPORT_ID,
@@ -6848,6 +6849,7 @@ const CONST = {
REPORT_ID: 'reportID',
BASE_62_REPORT_ID: 'base62ReportID',
TAX: 'tax',
+ EXCHANGE_RATE: 'exchangeRate',
REIMBURSABLE_TOTAL: 'reimbursableTotal',
NON_REIMBURSABLE_TOTAL: 'nonReimbursableTotal',
},
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index ac038e7f3435..de5625b95190 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -126,6 +126,7 @@ function mapTransactionItemToSelectedEntry(
),
action: item.action,
groupCurrency: item.groupCurrency,
+ groupExchangeRate: item.groupExchangeRate,
reportID: item.reportID,
policyID: item.report?.policyID,
amount: item.modifiedAmount ?? item.amount,
@@ -223,6 +224,7 @@ function prepareTransactionsList(
amount: Math.abs(item.modifiedAmount || item.amount),
groupAmount: item.groupAmount,
groupCurrency: item.groupCurrency,
+ groupExchangeRate: item.groupExchangeRate,
currency: item.currency,
isFromOneTransactionReport: isOneTransactionReport(item.report),
ownerAccountID: item.reportAction?.actorAccountID,
@@ -570,6 +572,7 @@ function Search({
amount: transactionItem.modifiedAmount ?? transactionItem.amount,
groupAmount: transactionItem.groupAmount,
groupCurrency: transactionItem.groupCurrency,
+ groupExchangeRate: transactionItem.groupExchangeRate,
currency: transactionItem.currency,
ownerAccountID: transactionItem.reportAction?.actorAccountID,
};
@@ -622,6 +625,7 @@ function Search({
amount: transactionItem.modifiedAmount ?? transactionItem.amount,
groupAmount: transactionItem.groupAmount,
groupCurrency: transactionItem.groupCurrency,
+ groupExchangeRate: transactionItem.groupExchangeRate,
currency: transactionItem.currency,
ownerAccountID: transactionItem.reportAction?.actorAccountID,
};
diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts
index e0997ba00990..74ad8c14fc44 100644
--- a/src/components/Search/types.ts
+++ b/src/components/Search/types.ts
@@ -56,6 +56,9 @@ type SelectedTransactionInfo = {
/** The group currency if the transaction is grouped. Defaults to the active policy currency if group has no target currency */
groupCurrency?: string;
+ /** The exchange rate of the transaction if the transaction is grouped. Defaults to the exchange rate against the active policy currency if group has no target currency */
+ groupExchangeRate?: number;
+
/** Whether it is the only expense of the parent expense report */
isFromOneTransactionReport?: boolean;
diff --git a/src/components/SelectionListWithSections/SearchTableHeader.tsx b/src/components/SelectionListWithSections/SearchTableHeader.tsx
index b3546a2637a6..27ec88f85a60 100644
--- a/src/components/SelectionListWithSections/SearchTableHeader.tsx
+++ b/src/components/SelectionListWithSections/SearchTableHeader.tsx
@@ -114,6 +114,10 @@ const getExpenseHeaders = (groupBy?: SearchGroupBy): SearchColumnConfig[] => [
translationKey: 'common.tax',
canBeMissing: true,
},
+ {
+ columnName: CONST.SEARCH.TABLE_COLUMNS.EXCHANGE_RATE,
+ translationKey: 'common.exchangeRate',
+ },
{
columnName: CONST.SEARCH.TABLE_COLUMNS.ORIGINAL_AMOUNT,
translationKey: 'common.originalAmount',
diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx
index c5f3add0e7b5..a495ee95300b 100644
--- a/src/components/TransactionItemRow/index.tsx
+++ b/src/components/TransactionItemRow/index.tsx
@@ -27,6 +27,7 @@ import {isExpenseReport, isSettled} from '@libs/ReportUtils';
import StringUtils from '@libs/StringUtils';
import {
getDescription,
+ getExchangeRate,
getMerchant,
getOriginalAmount,
getOriginalCurrency,
@@ -243,6 +244,8 @@ function TransactionItemRow({
}
}, [transactionItem, translate, report]);
+ const exchangeRateMessage = getExchangeRate(transactionItem);
+
const columnComponent = useMemo(
() => ({
[CONST.SEARCH.TABLE_COLUMNS.TYPE]: (
@@ -478,6 +481,11 @@ function TransactionItemRow({
/>
),
+ [CONST.SEARCH.TABLE_COLUMNS.EXCHANGE_RATE]: (
+
+
+
+ ),
[CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: (
{
diff --git a/src/languages/de.ts b/src/languages/de.ts
index d7b4707af93b..80dcd2164157 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -691,6 +691,7 @@ const translations: TranslationDeepObject = {
actionRequired: 'Aktion erforderlich',
duplicate: 'Duplizieren',
duplicated: 'Dupliziert',
+ exchangeRate: 'Wechselkurs',
reimbursableTotal: 'Erstattungsfähiger Gesamtbetrag',
nonReimbursableTotal: 'Nicht erstattungsfähiger Gesamtbetrag',
originalAmount: 'Ursprünglicher Betrag',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index d56abd3489b8..d4d66599c6c0 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -682,6 +682,7 @@ const translations = {
actionRequired: 'Action required',
duplicate: 'Duplicate',
duplicated: 'Duplicated',
+ exchangeRate: 'Exchange rate',
reimbursableTotal: 'Reimbursable total',
nonReimbursableTotal: 'Non-reimbursable total',
},
diff --git a/src/languages/es.ts b/src/languages/es.ts
index dedde21e08e7..d75bcacdb7f0 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -378,6 +378,7 @@ const translations: TranslationDeepObject = {
actionRequired: 'Acción requerida',
duplicate: 'Duplicar',
duplicated: 'Duplicado',
+ exchangeRate: 'Tipo de cambio',
reimbursableTotal: 'Total reembolsable',
nonReimbursableTotal: 'Total no reembolsable',
},
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 9a4a60d46bac..a98d3b427516 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -693,6 +693,7 @@ const translations: TranslationDeepObject = {
actionRequired: 'Action requise',
duplicate: 'Dupliquer',
duplicated: 'Dupliqué',
+ exchangeRate: 'Taux de change',
reimbursableTotal: 'Total remboursable',
nonReimbursableTotal: 'Total non remboursable',
originalAmount: 'Montant d’origine',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index ba77bd5b59ed..9a761598cfd9 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -692,6 +692,7 @@ const translations: TranslationDeepObject = {
actionRequired: 'Azione richiesta',
duplicate: 'Duplica',
duplicated: 'Duplicato',
+ exchangeRate: 'Tasso di cambio',
reimbursableTotal: 'Totale rimborsabile',
nonReimbursableTotal: 'Totale non rimborsabile',
originalAmount: 'Importo originale',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index f4d6eebd9981..28d56c69a6e5 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -691,6 +691,7 @@ const translations: TranslationDeepObject = {
actionRequired: '対応が必要',
duplicate: '複製',
duplicated: '重複',
+ exchangeRate: '為替レート',
reimbursableTotal: '経費精算対象の合計',
nonReimbursableTotal: '非払い戻し合計',
originalAmount: '元の金額',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 74558ae305a5..919cf4d608f4 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -692,6 +692,7 @@ const translations: TranslationDeepObject = {
actionRequired: 'Actie vereist',
duplicate: 'Dupliceren',
duplicated: 'Gedupliceerd',
+ exchangeRate: 'Wisselkoers',
reimbursableTotal: 'Totaal te vergoeden',
nonReimbursableTotal: 'Niet-vergoedbaar totaal',
originalAmount: 'Oorspronkelijk bedrag',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 1376bedd0c99..e8de68789f50 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -692,6 +692,7 @@ const translations: TranslationDeepObject = {
actionRequired: 'Wymagane działanie',
duplicate: 'Duplikat',
duplicated: 'Zduplikowano',
+ exchangeRate: 'Kurs wymiany',
reimbursableTotal: 'Łączna kwota podlegająca zwrotowi',
nonReimbursableTotal: 'Suma niepodlegająca zwrotowi',
originalAmount: 'Kwota pierwotna',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 4f314137f33a..03a5a38d879a 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -691,6 +691,7 @@ const translations: TranslationDeepObject = {
actionRequired: 'Ação necessária',
duplicate: 'Duplicar',
duplicated: 'Duplicado',
+ exchangeRate: 'Taxa de câmbio',
reimbursableTotal: 'Total reembolsável',
nonReimbursableTotal: 'Total não reembolsável',
originalAmount: 'Valor original',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 4570d036f1f4..6f730225bab4 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -688,6 +688,7 @@ const translations: TranslationDeepObject = {
actionRequired: '需要操作',
duplicate: '复制',
duplicated: '已重复',
+ exchangeRate: '汇率',
reimbursableTotal: '可报销总额',
nonReimbursableTotal: '不可报销总额',
originalAmount: '原始金额',
diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts
index 7cc3666de656..0469e4a43d4e 100644
--- a/src/libs/DebugUtils.ts
+++ b/src/libs/DebugUtils.ts
@@ -986,6 +986,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
case 'originalAmount':
case 'convertedAmount':
case 'groupAmount':
+ case 'groupExchangeRate':
return validateNumber(value);
case 'iouRequestType':
return validateConstantEnum(value, CONST.IOU.REQUEST_TYPE);
@@ -1107,6 +1108,7 @@ function validateTransactionDraftProperty(key: keyof Transaction, value: string)
taxValue: CONST.RED_BRICK_ROAD_PENDING_ACTION,
groupAmount: CONST.RED_BRICK_ROAD_PENDING_ACTION,
groupCurrency: CONST.RED_BRICK_ROAD_PENDING_ACTION,
+ groupExchangeRate: CONST.RED_BRICK_ROAD_PENDING_ACTION,
splitsStartDate: CONST.RED_BRICK_ROAD_PENDING_ACTION,
splitsEndDate: CONST.RED_BRICK_ROAD_PENDING_ACTION,
},
diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts
index 276d88f7b9b4..cfe15dfb4d2b 100644
--- a/src/libs/SearchUIUtils.ts
+++ b/src/libs/SearchUIUtils.ts
@@ -118,6 +118,7 @@ import {getIOUPayerAndReceiver} from './TransactionPreviewUtils';
import {
getCategory,
getDescription,
+ getExchangeRate,
getTag,
getTaxAmount,
getAmount as getTransactionAmount,
@@ -2221,6 +2222,14 @@ function getSortedTransactionData(
});
}
+ if (sortBy === CONST.SEARCH.TABLE_COLUMNS.EXCHANGE_RATE) {
+ return data.sort((a, b) => {
+ const aExchangeRate = getExchangeRate(a);
+ const bExchangeRate = getExchangeRate(b);
+ return compareValues(aExchangeRate, bExchangeRate, sortOrder, sortBy, localeCompare, true);
+ });
+ }
+
if (sortBy === CONST.SEARCH.TABLE_COLUMNS.TAX_RATE) {
return data.sort((a, b) => {
const aValue = `${a.policy?.taxRates?.taxes?.[a.taxCode ?? '']?.name ?? ''} (${a.policy?.taxRates?.taxes?.[a.taxCode ?? '']?.value ?? ''})`;
@@ -2546,6 +2555,8 @@ function getSearchColumnTranslationKey(columnId: SearchCustomColumnIds): Transla
return 'common.title';
case CONST.SEARCH.TABLE_COLUMNS.STATUS:
return 'common.status';
+ case CONST.SEARCH.TABLE_COLUMNS.EXCHANGE_RATE:
+ return 'common.exchangeRate';
case CONST.SEARCH.TABLE_COLUMNS.POLICY_NAME:
return 'workspace.common.workspace';
case CONST.SEARCH.TABLE_COLUMNS.CARD:
@@ -3069,6 +3080,7 @@ function getColumnsToShow(
[CONST.SEARCH.TABLE_COLUMNS.BILLABLE]: false,
[CONST.SEARCH.TABLE_COLUMNS.TAX_RATE]: false,
[CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT]: false,
+ [CONST.SEARCH.TABLE_COLUMNS.EXCHANGE_RATE]: false,
[CONST.SEARCH.TABLE_COLUMNS.ORIGINAL_AMOUNT]: false,
[CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: true,
[CONST.SEARCH.TABLE_COLUMNS.BASE_62_REPORT_ID]: false,
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 982460203e3c..b52233516379 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -9,6 +9,7 @@ import type {ValueOf} from 'type-fest';
import type {Coordinate} from '@components/MapView/MapViewTypes';
import utils from '@components/MapView/utils';
import type {UnreportedExpenseListItemType} from '@components/SelectionListWithSections/types';
+import type {TransactionWithOptionalSearchFields} from '@components/TransactionItemRow';
import {getPolicyTagsData} from '@libs/actions/Policy/Tag';
import type {MergeDuplicatesParams} from '@libs/API/parameters';
import {getCategoryDefaultTaxRate} from '@libs/CategoryUtils';
@@ -1047,6 +1048,24 @@ function getTagArrayFromName(tagName: string): string[] {
return newMatches;
}
+/**
+ * Returns the exchange rate for a transaction, based on its group
+ */
+function getExchangeRate(transaction: TransactionWithOptionalSearchFields) {
+ const fromCurrency = getCurrency(transaction);
+ const toCurrency = transaction.groupCurrency ?? fromCurrency;
+
+ if (!transaction.groupExchangeRate) {
+ return '';
+ }
+
+ if (Number(transaction.groupExchangeRate) === 1) {
+ return '';
+ }
+
+ return transaction.groupExchangeRate ? `${transaction.groupExchangeRate} ${fromCurrency}/${toCurrency}` : '';
+}
+
/**
* Return the tag from the transaction. When the tagIndex is passed, return the tag based on the index.
* This "tag" field has no "modified" complement.
@@ -2407,6 +2426,7 @@ export {
mergeProhibitedViolations,
getOriginalAttendees,
getReportOwnerAsAttendee,
+ getExchangeRate,
shouldReuseInitialTransaction,
};
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 54d8507c09f4..53469413b5fb 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -1811,6 +1811,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
case CONST.SEARCH.TABLE_COLUMNS.TITLE:
case CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION:
case CONST.SEARCH.TABLE_COLUMNS.IN:
+ case CONST.SEARCH.TABLE_COLUMNS.EXCHANGE_RATE:
default:
columnWidth = styles.flex1;
}
diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts
index b4c1b566c49c..03817227b3c4 100644
--- a/src/types/onyx/SearchResults.ts
+++ b/src/types/onyx/SearchResults.ts
@@ -170,6 +170,9 @@ type SearchTransaction = {
/** The group currency if the transaction is grouped. Defaults to the active policy currency if group has no target currency */
groupCurrency?: string;
+ /** The exchange rate of the transaction if the transaction is grouped. Defaults to the exchange rate against the active policy currency if group has no target currency */
+ groupExchangeRate?: number;
+
/** Reimbursable status of the transaction */
reimbursable?: boolean;
diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts
index 715f46d494e9..3ff93f59613b 100644
--- a/src/types/onyx/Transaction.ts
+++ b/src/types/onyx/Transaction.ts
@@ -451,6 +451,9 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback<
/** The group currency if the transaction is grouped. Defaults to the active policy currency if group has no target currency */
groupCurrency?: string;
+ /** The exchange rate of the transaction if the transaction is grouped. Defaults to the exchange rate against the active policy currency if group has no target currency */
+ groupExchangeRate?: number;
+
/** Used during the creation flow before the transaction is saved to the server */
iouRequestType?: IOURequestType;