Merge pull request #340 from bigcapitalhq/big-65-get-realtime-exchange-rate-from-third-party-service

feat: get latest exchange rate from third party services
This commit is contained in:
Ahmed Bouhuolia
2024-01-29 18:44:05 +02:00
committed by GitHub
13 changed files with 327 additions and 414 deletions

View File

@@ -57,4 +57,10 @@ GOTENBERG_DOCS_URL=http://server:3000/public/
# Gotenberg API - (development)
# GOTENBERG_URL=http://localhost:9000
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/
# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/
# Exchange Rate Service
EXCHANGE_RATE_SERVICE=open-exchange-rate
# Open Exchange Rate
OPEN_EXCHANGE_RATE_APP_ID=

View File

@@ -1,19 +1,16 @@
import { Service, Inject } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
import { query, oneOf } from 'express-validator';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from './BaseController';
import { ServiceError } from '@/exceptions';
import ExchangeRatesService from '@/services/ExchangeRates/ExchangeRatesService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { EchangeRateErrors } from '@/lib/ExchangeRate/types';
import { ExchangeRateApplication } from '@/services/ExchangeRates/ExchangeRateApplication';
@Service()
export default class ExchangeRatesController extends BaseController {
@Inject()
exchangeRatesService: ExchangeRatesService;
@Inject()
dynamicListService: DynamicListingService;
private exchangeRatesApp: ExchangeRateApplication;
/**
* Constructor method.
@@ -22,164 +19,40 @@ export default class ExchangeRatesController extends BaseController {
const router = Router();
router.get(
'/',
[...this.exchangeRatesListSchema],
'/latest',
[
oneOf([
query('to_currency').exists().isString().isISO4217(),
query('from_currency').exists().isString().isISO4217(),
]),
],
this.validationResult,
asyncMiddleware(this.exchangeRates.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
this.handleServiceError,
);
router.post(
'/',
[...this.exchangeRateDTOSchema],
this.validationResult,
asyncMiddleware(this.addExchangeRate.bind(this)),
this.handleServiceError
);
router.post(
'/:id',
[...this.exchangeRateEditDTOSchema, ...this.exchangeRateIdSchema],
this.validationResult,
asyncMiddleware(this.editExchangeRate.bind(this)),
this.handleServiceError
);
router.delete(
'/:id',
[...this.exchangeRateIdSchema],
this.validationResult,
asyncMiddleware(this.deleteExchangeRate.bind(this)),
asyncMiddleware(this.latestExchangeRate.bind(this)),
this.handleServiceError
);
return router;
}
get exchangeRatesListSchema() {
return [
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('column_sort_by').optional(),
query('sort_order').optional().isIn(['desc', 'asc']),
];
}
get exchangeRateDTOSchema() {
return [
check('exchange_rate').exists().isNumeric().toFloat(),
check('currency_code').exists().trim().escape(),
check('date').exists().isISO8601(),
];
}
get exchangeRateEditDTOSchema() {
return [check('exchange_rate').exists().isNumeric().toFloat()];
}
get exchangeRateIdSchema() {
return [param('id').isNumeric().toInt()];
}
get exchangeRatesIdsSchema() {
return [
query('ids').isArray({ min: 2 }),
query('ids.*').isNumeric().toInt(),
];
}
/**
* Retrieve exchange rates.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async exchangeRates(req: Request, res: Response, next: NextFunction) {
private async latestExchangeRate(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = {
page: 1,
pageSize: 12,
filterRoles: [],
columnSortBy: 'created_at',
sortOrder: 'asc',
...this.matchedQueryData(req),
};
if (filter.stringifiedFilterRoles) {
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
}
try {
const exchangeRates = await this.exchangeRatesService.listExchangeRates(
tenantId,
filter
);
return res.status(200).send({ exchange_rates: exchangeRates });
} catch (error) {
next(error);
}
}
/**
* Adds a new exchange rate on the given date.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async addExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const exchangeRateDTO = this.matchedBodyData(req);
const exchangeRateQuery = this.matchedQueryData(req);
try {
const exchangeRate = await this.exchangeRatesService.newExchangeRate(
const exchangeRate = await this.exchangeRatesApp.latest(
tenantId,
exchangeRateDTO
exchangeRateQuery
);
return res.status(200).send({ id: exchangeRate.id });
} catch (error) {
next(error);
}
}
/**
* Edit the given exchange rate.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async editExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: exchangeRateId } = req.params;
const exchangeRateDTO = this.matchedBodyData(req);
try {
const exchangeRate = await this.exchangeRatesService.editExchangeRate(
tenantId,
exchangeRateId,
exchangeRateDTO
);
return res.status(200).send({
id: exchangeRateId,
message: 'The exchange rate has been edited successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Delete the given exchange rate from the storage.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async deleteExchangeRate(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req;
const { id: exchangeRateId } = req.params;
try {
await this.exchangeRatesService.deleteExchangeRate(
tenantId,
exchangeRateId
);
return res.status(200).send({ id: exchangeRateId });
return res.status(200).send(exchangeRate);
} catch (error) {
next(error);
}
@@ -192,26 +65,56 @@ export default class ExchangeRatesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
handleServiceError(
private handleServiceError(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'EXCHANGE_RATE_NOT_FOUND') {
return res.status(404).send({
errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
});
}
if (error.errorType === 'NOT_FOUND_EXCHANGE_RATES') {
if (EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY === error.errorType) {
return res.status(400).send({
errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 100 }],
errors: [
{
type: EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY,
code: 100,
message: 'The given base currency is invalid.',
},
],
});
}
if (error.errorType === 'EXCHANGE_RATE_PERIOD_EXISTS') {
} else if (
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED === error.errorType
) {
return res.status(400).send({
errors: [{ type: 'EXCHANGE.RATE.PERIOD.EXISTS', code: 300 }],
errors: [
{
type: EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED,
code: 200,
message: 'The service is not allowed',
},
],
});
} else if (
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED === error.errorType
) {
return res.status(400).send({
errors: [
{
type: EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
code: 300,
message: 'The API key is required',
},
],
});
} else if (EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED === error.errorType) {
return res.status(400).send({
errors: [
{
type: EchangeRateErrors.EX_RATE_LIMIT_EXCEEDED,
code: 400,
message: 'The API rate limit has been exceeded',
},
],
});
}
}

View File

@@ -169,4 +169,14 @@ module.exports = {
* to application detarmines to upgrade.
*/
databaseBatch: 4,
/**
* Exchange rate.
*/
exchangeRate: {
service: 'open-exchange-rate',
openExchangeRate: {
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
}
}
};

View File

@@ -1,36 +1,10 @@
import { IFilterRole } from './DynamicFilter';
export interface ExchangeRateLatestDTO {
toCurrency: string;
fromCurrency: string;
}
export interface IExchangeRate {
id: number,
currencyCode: string,
exchangeRate: number,
date: Date,
createdAt: Date,
updatedAt: Date,
};
export interface IExchangeRateDTO {
currencyCode: string,
exchangeRate: number,
date: Date,
};
export interface IExchangeRateEditDTO {
exchangeRate: number,
};
export interface IExchangeRateFilter {
page: number,
pageSize: number,
filterRoles?: IFilterRole[];
columnSortBy: string;
sortOrder: string;
};
export interface IExchangeRatesService {
newExchangeRate(tenantId: number, exchangeRateDTO: IExchangeRateDTO): Promise<IExchangeRate>;
editExchangeRate(tenantId: number, exchangeRateId: number, editExRateDTO: IExchangeRateEditDTO): Promise<void>;
deleteExchangeRate(tenantId: number, exchangeRateId: number): Promise<void>;
listExchangeRates(tenantId: number, exchangeRateFilter: IExchangeRateFilter): Promise<void>;
};
export interface EchangeRateLatestPOJO {
baseCurrency: string;
toCurrency: string;
exchangeRate: number;
}

View File

@@ -0,0 +1,45 @@
import { OpenExchangeRate } from './OpenExchangeRate';
import { ExchangeRateServiceType, IExchangeRateService } from './types';
export class ExchangeRate {
private exchangeRateService: IExchangeRateService;
private exchangeRateServiceType: ExchangeRateServiceType;
/**
* Constructor method.
* @param {ExchangeRateServiceType} service
*/
constructor(service: ExchangeRateServiceType) {
this.exchangeRateServiceType = service;
this.initService();
}
/**
* Initialize the exchange rate service based on the service type.
*/
private initService() {
if (
this.exchangeRateServiceType === ExchangeRateServiceType.OpenExchangeRate
) {
this.setExchangeRateService(new OpenExchangeRate());
}
}
/**
* Sets the exchange rate service.
* @param {IExchangeRateService} service
*/
private setExchangeRateService(service: IExchangeRateService) {
this.exchangeRateService = service;
}
/**
* Gets the latest exchange rate.
* @param {string} baseCurrency
* @param {string} toCurrency
* @returns {number}
*/
public latest(baseCurrency: string, toCurrency: string): Promise<number> {
return this.exchangeRateService.latest(baseCurrency, toCurrency);
}
}

View File

@@ -0,0 +1,81 @@
import Axios, { AxiosError } from 'axios';
import {
EchangeRateErrors,
IExchangeRateService,
OPEN_EXCHANGE_RATE_LATEST_URL,
} from './types';
import config from '@/config';
import { ServiceError } from '@/exceptions';
export class OpenExchangeRate implements IExchangeRateService {
/**
* Gets the latest exchange rate.
* @param {string} baseCurrency
* @param {string} toCurrency
* @returns {Promise<number}
*/
public async latest(
baseCurrency: string,
toCurrency: string
): Promise<number> {
// Vaclidates the Open Exchange Rate api id early.
this.validateApiIdExistance();
try {
const result = await Axios.get(OPEN_EXCHANGE_RATE_LATEST_URL, {
params: {
app_id: config.exchangeRate.openExchangeRate.appId,
base: baseCurrency,
symbols: toCurrency,
},
});
return result.data.rates[toCurrency] || (1 as number);
} catch (error) {
this.handleLatestErrors(error);
}
}
/**
* Validates the Open Exchange Rate api id.
* @throws {ServiceError}
*/
private validateApiIdExistance() {
const apiId = config.exchangeRate.openExchangeRate.appId;
if (!apiId) {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
}
}
/**
* Handles the latest errors.
* @param {any} error
* @throws {ServiceError}
*/
private handleLatestErrors(error: any) {
if (error.response.data?.message === 'missing_app_id') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
} else if (error.response.data?.message === 'invalid_app_id') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_API_KEY_REQUIRED,
'Invalid App ID provided. Please sign up at https://openexchangerates.org/signup, or contact support@openexchangerates.org.'
);
} else if (error.response.data?.message === 'not_allowed') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED,
'Getting the exchange rate from the given base currency to the given currency is not allowed.'
);
} else if (error.response.data?.message === 'invalid_base') {
throw new ServiceError(
EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY,
'The given base currency is invalid.'
);
}
}
}

View File

@@ -0,0 +1,17 @@
export interface IExchangeRateService {
latest(baseCurrency: string, toCurrency: string): Promise<number>;
}
export enum ExchangeRateServiceType {
OpenExchangeRate = 'OpenExchangeRate',
}
export enum EchangeRateErrors {
EX_RATE_SERVICE_NOT_ALLOWED = 'EX_RATE_SERVICE_NOT_ALLOWED',
EX_RATE_LIMIT_EXCEEDED = 'EX_RATE_LIMIT_EXCEEDED',
EX_RATE_SERVICE_API_KEY_REQUIRED = 'EX_RATE_SERVICE_API_KEY_REQUIRED',
EX_RATE_INVALID_BASE_CURRENCY = 'EX_RATE_INVALID_BASE_CURRENCY',
}
export const OPEN_EXCHANGE_RATE_LATEST_URL =
'https://openexchangerates.org/api/latest.json';

View File

@@ -0,0 +1,21 @@
import { Inject } from 'typedi';
import { ExchangeRatesService } from './ExchangeRatesService';
import { EchangeRateLatestPOJO, ExchangeRateLatestDTO } from '@/interfaces';
export class ExchangeRateApplication {
@Inject()
private exchangeRateService: ExchangeRatesService;
/**
* Gets the latest exchange rate.
* @param {number} tenantId
* @param {ExchangeRateLatestDTO} exchangeRateLatestDTO
* @returns {Promise<EchangeRateLatestPOJO>}
*/
public latest(
tenantId: number,
exchangeRateLatestDTO: ExchangeRateLatestDTO
): Promise<EchangeRateLatestPOJO> {
return this.exchangeRateService.latest(tenantId, exchangeRateLatestDTO);
}
}

View File

@@ -1,193 +1,37 @@
import moment from 'moment';
import { difference } from 'lodash';
import { Service, Inject } from 'typedi';
import { ServiceError } from '@/exceptions';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import {
EventDispatcher,
EventDispatcherInterface,
} from 'decorators/eventDispatcher';
import {
IExchangeRateDTO,
IExchangeRate,
IExchangeRatesService,
IExchangeRateEditDTO,
IExchangeRateFilter,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
const ERRORS = {
NOT_FOUND_EXCHANGE_RATES: 'NOT_FOUND_EXCHANGE_RATES',
EXCHANGE_RATE_PERIOD_EXISTS: 'EXCHANGE_RATE_PERIOD_EXISTS',
EXCHANGE_RATE_NOT_FOUND: 'EXCHANGE_RATE_NOT_FOUND',
};
import { Service } from 'typedi';
import { ExchangeRate } from '@/lib/ExchangeRate/ExchangeRate';
import { ExchangeRateServiceType } from '@/lib/ExchangeRate/types';
import { EchangeRateLatestPOJO, ExchangeRateLatestDTO } from '@/interfaces';
import { TenantMetadata } from '@/system/models';
@Service()
export default class ExchangeRatesService implements IExchangeRatesService {
@Inject('logger')
logger: any;
@EventDispatcher()
eventDispatcher: EventDispatcherInterface;
@Inject()
tenancy: TenancyService;
@Inject()
dynamicListService: DynamicListingService;
export class ExchangeRatesService {
/**
* Creates a new exchange rate.
* Gets the latest exchange rate.
* @param {number} tenantId
* @param {IExchangeRateDTO} exchangeRateDTO
* @returns {Promise<IExchangeRate>}
* @param {number} exchangeRateLatestDTO
* @returns {EchangeRateLatestPOJO}
*/
public async newExchangeRate(
public async latest(
tenantId: number,
exchangeRateDTO: IExchangeRateDTO
): Promise<IExchangeRate> {
const { ExchangeRate } = this.tenancy.models(tenantId);
exchangeRateLatestDTO: ExchangeRateLatestDTO
): Promise<EchangeRateLatestPOJO> {
const organization = await TenantMetadata.query().findOne({ tenantId });
this.logger.info('[exchange_rates] trying to insert new exchange rate.', {
tenantId,
exchangeRateDTO,
});
await this.validateExchangeRatePeriodExistance(tenantId, exchangeRateDTO);
// Assign the organization base currency as a default currency
// if no currency is provided
const fromCurrency =
exchangeRateLatestDTO.fromCurrency || organization.baseCurrency;
const toCurrency =
exchangeRateLatestDTO.toCurrency || organization.baseCurrency;
const exchangeRate = await ExchangeRate.query().insertAndFetch({
...exchangeRateDTO,
date: moment(exchangeRateDTO.date).format('YYYY-MM-DD'),
});
this.logger.info('[exchange_rates] inserted successfully.', {
tenantId,
exchangeRateDTO,
});
return exchangeRate;
}
const exchange = new ExchangeRate(ExchangeRateServiceType.OpenExchangeRate);
const exchangeRate = await exchange.latest(fromCurrency, toCurrency);
/**
* Edits the exchange rate details.
* @param {number} tenantId - Tenant id.
* @param {number} exchangeRateId - Exchange rate id.
* @param {IExchangeRateEditDTO} editExRateDTO - Edit exchange rate DTO.
*/
public async editExchangeRate(
tenantId: number,
exchangeRateId: number,
editExRateDTO: IExchangeRateEditDTO
): Promise<void> {
const { ExchangeRate } = this.tenancy.models(tenantId);
this.logger.info('[exchange_rates] trying to edit exchange rate.', {
tenantId,
exchangeRateId,
editExRateDTO,
});
await this.validateExchangeRateExistance(tenantId, exchangeRateId);
await ExchangeRate.query()
.where('id', exchangeRateId)
.update({ ...editExRateDTO });
this.logger.info('[exchange_rates] exchange rate edited successfully.', {
tenantId,
exchangeRateId,
editExRateDTO,
});
}
/**
* Deletes the given exchange rate.
* @param {number} tenantId - Tenant id.
* @param {number} exchangeRateId - Exchange rate id.
*/
public async deleteExchangeRate(
tenantId: number,
exchangeRateId: number
): Promise<void> {
const { ExchangeRate } = this.tenancy.models(tenantId);
await this.validateExchangeRateExistance(tenantId, exchangeRateId);
await ExchangeRate.query().findById(exchangeRateId).delete();
}
/**
* Listing exchange rates details.
* @param {number} tenantId - Tenant id.
* @param {IExchangeRateFilter} exchangeRateFilter - Exchange rates list filter.
*/
public async listExchangeRates(
tenantId: number,
exchangeRateFilter: IExchangeRateFilter
): Promise<void> {
const { ExchangeRate } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
ExchangeRate,
exchangeRateFilter
);
// Retrieve exchange rates by the given query.
const exchangeRates = await ExchangeRate.query()
.onBuild((query) => {
dynamicFilter.buildQuery()(query);
})
.pagination(exchangeRateFilter.page - 1, exchangeRateFilter.pageSize);
return exchangeRates;
}
/**
* Validates period of the exchange rate existance.
* @param {number} tenantId - Tenant id.
* @param {IExchangeRateDTO} exchangeRateDTO - Exchange rate DTO.
* @return {Promise<void>}
*/
private async validateExchangeRatePeriodExistance(
tenantId: number,
exchangeRateDTO: IExchangeRateDTO
): Promise<void> {
const { ExchangeRate } = this.tenancy.models(tenantId);
this.logger.info('[exchange_rates] trying to validate period existance.', {
tenantId,
});
const foundExchangeRate = await ExchangeRate.query()
.where('currency_code', exchangeRateDTO.currencyCode)
.where('date', exchangeRateDTO.date);
if (foundExchangeRate.length > 0) {
this.logger.info('[exchange_rates] given exchange rate period exists.', {
tenantId,
});
throw new ServiceError(ERRORS.EXCHANGE_RATE_PERIOD_EXISTS);
}
}
/**
* Validate the given echange rate id existance.
* @param {number} tenantId - Tenant id.
* @param {number} exchangeRateId - Exchange rate id.
* @returns {Promise<void>}
*/
private async validateExchangeRateExistance(
tenantId: number,
exchangeRateId: number
) {
const { ExchangeRate } = this.tenancy.models(tenantId);
this.logger.info(
'[exchange_rates] trying to validate exchange rate id existance.',
{ tenantId, exchangeRateId }
);
const foundExchangeRate = await ExchangeRate.query().findById(
exchangeRateId
);
if (!foundExchangeRate) {
this.logger.info('[exchange_rates] exchange rate not found.', {
tenantId,
exchangeRateId,
});
throw new ServiceError(ERRORS.EXCHANGE_RATE_NOT_FOUND);
}
return {
baseCurrency: fromCurrency,
toCurrency: exchangeRateLatestDTO.toCurrency,
exchangeRate,
};
}
}

View File

@@ -1,6 +1,8 @@
import BaseModel from 'models/Model';
export default class TenantMetadata extends BaseModel {
baseCurrency: string;
/**
* Table name.
*/

View File

@@ -1,6 +1,5 @@
import { useExchangeRate } from '@/hooks/query';
import { useCurrentOrganization } from '@/hooks/state';
import React from 'react';
import { useLatestExchangeRate } from '@/hooks/query';
interface AutoExchangeRateProviderProps {
children: React.ReactNode;
@@ -18,16 +17,19 @@ const AutoExchangeRateContext = React.createContext(
function AutoExchangeRateProvider({ children }: AutoExchangeRateProviderProps) {
const [autoExRateCurrency, setAutoExRateCurrency] =
React.useState<string>('');
const currentOrganization = useCurrentOrganization();
// Retrieves the exchange rate.
const { data: autoExchangeRate, isLoading: isAutoExchangeRateLoading } =
useExchangeRate(autoExRateCurrency, currentOrganization.base_currency, {
enabled: Boolean(currentOrganization.base_currency && autoExRateCurrency),
refetchOnWindowFocus: false,
staleTime: 0,
cacheTime: 0,
});
useLatestExchangeRate(
{ fromCurrency: autoExRateCurrency },
{
enabled: Boolean(autoExRateCurrency),
refetchOnWindowFocus: false,
staleTime: 0,
cacheTime: 0,
retry: 0,
},
);
const value = {
autoExRateCurrency,

View File

@@ -98,24 +98,30 @@ interface UseSyncExRateToFormProps {
*/
export const useSyncExRateToForm = ({ onSynced }: UseSyncExRateToFormProps) => {
const { setFieldValue, values } = useFormikContext();
const { autoExRateCurrency, autoExchangeRate } = useAutoExRateContext();
const { autoExRateCurrency, autoExchangeRate, isAutoExchangeRateLoading } =
useAutoExRateContext();
const updateEntriesOnExChange = useUpdateEntriesOnExchangeRateChange();
// Sync the fetched real-time exchanage rate to the form.
useEffect(() => {
if (autoExchangeRate?.exchange_rate && autoExRateCurrency) {
setFieldValue('exchange_rate', autoExchangeRate?.exchange_rate + '');
if (!isAutoExchangeRateLoading && autoExRateCurrency) {
// Sets a default ex. rate to 1 in case the exchange rate service wasn't configured.
// or returned an error from the server-side.
const exchangeRate = autoExchangeRate?.exchange_rate || 1;
setFieldValue('exchange_rate', exchangeRate + '');
setFieldValue(
'entries',
updateEntriesOnExChange(
values.exchange_rate,
autoExchangeRate?.exchange_rate,
),
updateEntriesOnExChange(values.exchange_rate, exchangeRate),
);
onSynced?.();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autoExchangeRate?.exchange_rate, autoExRateCurrency]);
}, [
autoExchangeRate?.exchange_rate,
autoExRateCurrency,
isAutoExchangeRateLoading,
]);
return null;
};

View File

@@ -1,34 +1,36 @@
// @ts-nocheck
import { useQuery } from 'react-query';
import QUERY_TYPES from './types';
import useApiRequest from '../useRequest';
function getRandomItemFromArray(arr) {
const randomIndex = Math.floor(Math.random() * arr.length);
return arr[randomIndex];
}
function delay(t, val) {
return new Promise((resolve) => setTimeout(resolve, t, val));
interface LatestExchangeRateQuery {
fromCurrency?: string;
toCurrency?: string;
}
/**
* Retrieves tax rates.
* Retrieves latest exchange rate.
* @param {number} customerId - Customer id.
*/
export function useExchangeRate(
fromCurrency: string,
toCurrency: string,
export function useLatestExchangeRate(
{ toCurrency, fromCurrency }: LatestExchangeRateQuery,
props,
) {
return useQuery(
[QUERY_TYPES.EXCHANGE_RATE, fromCurrency, toCurrency],
async () => {
await delay(100);
const apiRequest = useApiRequest();
return {
from_currency: fromCurrency,
to_currency: toCurrency,
exchange_rate: 1.00,
};
},
return useQuery(
[QUERY_TYPES.EXCHANGE_RATE, toCurrency, fromCurrency],
() =>
apiRequest
.http({
url: `/api/exchange_rates/latest`,
method: 'get',
params: {
to_currency: toCurrency,
from_currency: fromCurrency,
},
})
.then((res) => res.data),
props,
);
}