mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat: get latest exchange rate from third party services
This commit is contained in:
@@ -57,4 +57,10 @@ GOTENBERG_DOCS_URL=http://server:3000/public/
|
|||||||
|
|
||||||
# Gotenberg API - (development)
|
# Gotenberg API - (development)
|
||||||
# GOTENBERG_URL=http://localhost:9000
|
# 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_I=
|
||||||
@@ -4,16 +4,13 @@ import { check, param, query } from 'express-validator';
|
|||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import BaseController from './BaseController';
|
import BaseController from './BaseController';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import ExchangeRatesService from '@/services/ExchangeRates/ExchangeRatesService';
|
import { ExchangeRatesService } from '@/services/ExchangeRates/ExchangeRatesService';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import { EchangeRateErrors } from '@/lib/ExchangeRate/types';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ExchangeRatesController extends BaseController {
|
export default class ExchangeRatesController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
exchangeRatesService: ExchangeRatesService;
|
private exchangeRatesService: ExchangeRatesService;
|
||||||
|
|
||||||
@Inject()
|
|
||||||
dynamicListService: DynamicListingService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -22,164 +19,35 @@ export default class ExchangeRatesController extends BaseController {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/latest',
|
||||||
[...this.exchangeRatesListSchema],
|
[query('to_currency').exists().isString()],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.exchangeRates.bind(this)),
|
asyncMiddleware(this.latestExchangeRate.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)),
|
|
||||||
this.handleServiceError
|
this.handleServiceError
|
||||||
);
|
);
|
||||||
return router;
|
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.
|
* Retrieve exchange rates.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async exchangeRates(req: Request, res: Response, next: NextFunction) {
|
private async latestExchangeRate(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const exchangeRateQuery = this.matchedQueryData(req);
|
||||||
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);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exchangeRate = await this.exchangeRatesService.newExchangeRate(
|
const exchangeRate = await this.exchangeRatesService.latest(
|
||||||
tenantId,
|
tenantId,
|
||||||
exchangeRateDTO
|
exchangeRateQuery
|
||||||
);
|
);
|
||||||
return res.status(200).send({ id: exchangeRate.id });
|
return res.status(200).send(exchangeRate);
|
||||||
} 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 });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -192,26 +60,56 @@ export default class ExchangeRatesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
handleServiceError(
|
private handleServiceError(
|
||||||
error: Error,
|
error: Error,
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'EXCHANGE_RATE_NOT_FOUND') {
|
if (EchangeRateErrors.EX_RATE_INVALID_BASE_CURRENCY === error.errorType) {
|
||||||
return res.status(404).send({
|
|
||||||
errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (error.errorType === 'NOT_FOUND_EXCHANGE_RATES') {
|
|
||||||
return res.status(400).send({
|
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.',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
} else if (
|
||||||
if (error.errorType === 'EXCHANGE_RATE_PERIOD_EXISTS') {
|
EchangeRateErrors.EX_RATE_SERVICE_NOT_ALLOWED === error.errorType
|
||||||
|
) {
|
||||||
return res.status(400).send({
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,4 +169,14 @@ module.exports = {
|
|||||||
* to application detarmines to upgrade.
|
* to application detarmines to upgrade.
|
||||||
*/
|
*/
|
||||||
databaseBatch: 4,
|
databaseBatch: 4,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange rate.
|
||||||
|
*/
|
||||||
|
exchangeRate: {
|
||||||
|
service: 'open-exchange-rate',
|
||||||
|
openExchangeRate: {
|
||||||
|
appId: process.env.OPEN_EXCHANGE_RATE_APP_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
45
packages/server/src/lib/ExchangeRate/ExchangeRate.ts
Normal file
45
packages/server/src/lib/ExchangeRate/ExchangeRate.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts
Normal file
62
packages/server/src/lib/ExchangeRate/OpenExchangeRate.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
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> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the latest errors.
|
||||||
|
* @param {any} error
|
||||||
|
*/
|
||||||
|
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.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
packages/server/src/lib/ExchangeRate/types.ts
Normal file
17
packages/server/src/lib/ExchangeRate/types.ts
Normal 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';
|
||||||
@@ -1,193 +1,39 @@
|
|||||||
import moment from 'moment';
|
|
||||||
import { difference } from 'lodash';
|
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { IExchangeRatesService } from '@/interfaces';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import { ExchangeRate } from '@/lib/ExchangeRate/ExchangeRate';
|
||||||
import {
|
import { ExchangeRateServiceType } from '@/lib/ExchangeRate/types';
|
||||||
EventDispatcher,
|
interface ExchangeRateLatestDTO {
|
||||||
EventDispatcherInterface,
|
toCurrency: string;
|
||||||
} from 'decorators/eventDispatcher';
|
}
|
||||||
import {
|
|
||||||
IExchangeRateDTO,
|
|
||||||
IExchangeRate,
|
|
||||||
IExchangeRatesService,
|
|
||||||
IExchangeRateEditDTO,
|
|
||||||
IExchangeRateFilter,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
|
|
||||||
const ERRORS = {
|
interface EchangeRateLatestPOJO {
|
||||||
NOT_FOUND_EXCHANGE_RATES: 'NOT_FOUND_EXCHANGE_RATES',
|
baseCurrency: string;
|
||||||
EXCHANGE_RATE_PERIOD_EXISTS: 'EXCHANGE_RATE_PERIOD_EXISTS',
|
toCurrency: string;
|
||||||
EXCHANGE_RATE_NOT_FOUND: 'EXCHANGE_RATE_NOT_FOUND',
|
exchangeRate: number;
|
||||||
};
|
}
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ExchangeRatesService implements IExchangeRatesService {
|
export class ExchangeRatesService {
|
||||||
@Inject('logger')
|
|
||||||
logger: any;
|
|
||||||
|
|
||||||
@EventDispatcher()
|
|
||||||
eventDispatcher: EventDispatcherInterface;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
tenancy: TenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
dynamicListService: DynamicListingService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new exchange rate.
|
* Gets the latest exchange rate.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IExchangeRateDTO} exchangeRateDTO
|
* @param {number} exchangeRateLatestDTO
|
||||||
* @returns {Promise<IExchangeRate>}
|
* @returns {EchangeRateLatestPOJO}
|
||||||
*/
|
*/
|
||||||
public async newExchangeRate(
|
public async latest(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
exchangeRateDTO: IExchangeRateDTO
|
exchangeRateLatestDTO: ExchangeRateLatestDTO
|
||||||
): Promise<IExchangeRate> {
|
): Promise<EchangeRateLatestPOJO> {
|
||||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
const exchange = new ExchangeRate(ExchangeRateServiceType.OpenExchangeRate);
|
||||||
|
const exchangeRate = await exchange.latest(
|
||||||
this.logger.info('[exchange_rates] trying to insert new exchange rate.', {
|
'USD',
|
||||||
tenantId,
|
exchangeRateLatestDTO.toCurrency
|
||||||
exchangeRateDTO,
|
|
||||||
});
|
|
||||||
await this.validateExchangeRatePeriodExistance(tenantId, exchangeRateDTO);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
return {
|
||||||
this.logger.info('[exchange_rates] exchange rate not found.', {
|
baseCurrency: 'USD',
|
||||||
tenantId,
|
toCurrency: exchangeRateLatestDTO.toCurrency,
|
||||||
exchangeRateId,
|
exchangeRate,
|
||||||
});
|
};
|
||||||
throw new ServiceError(ERRORS.EXCHANGE_RATE_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user