mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
refactor: currencies service.
refactor: exchange rates service.
This commit is contained in:
@@ -85,6 +85,7 @@ export default class AccountsController extends BaseController{
|
||||
router.delete(
|
||||
'/',
|
||||
this.bulkDeleteSchema,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteBulkAccounts.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
);
|
||||
@@ -144,6 +145,9 @@ export default class AccountsController extends BaseController{
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
get bulkDeleteSchema() {
|
||||
return [
|
||||
query('ids').isArray({ min: 2 }),
|
||||
|
||||
@@ -5,10 +5,19 @@ import { mapKeysDeep } from 'utils'
|
||||
|
||||
export default class BaseController {
|
||||
|
||||
/**
|
||||
* Converts plain object keys to cameCase style.
|
||||
* @param {Object} data
|
||||
*/
|
||||
private dataToCamelCase(data) {
|
||||
return mapKeysDeep(data, (v, k) => camelCase(k));
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the body data from validation schema.
|
||||
* @param {Request} req
|
||||
* @param options
|
||||
*/
|
||||
matchedBodyData(req: Request, options: any = {}) {
|
||||
const data = matchedData(req, {
|
||||
locations: ['body'],
|
||||
@@ -18,6 +27,10 @@ export default class BaseController {
|
||||
return this.dataToCamelCase(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the query data from validation schema.
|
||||
* @param {Request} req
|
||||
*/
|
||||
matchedQueryData(req: Request) {
|
||||
const data = matchedData(req, {
|
||||
locations: ['query'],
|
||||
@@ -25,6 +38,12 @@ export default class BaseController {
|
||||
return this.dataToCamelCase(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate validation schema middleware.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
validationResult(req: Request, res: Response, next: NextFunction) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { check, param, query } from 'express-validator';
|
||||
import { check, param, query, ValidationChain } from 'express-validator';
|
||||
import BaseController from "api/controllers/BaseController";
|
||||
|
||||
export default class ContactsController extends BaseController {
|
||||
|
||||
/**
|
||||
* Contact DTO schema.
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get contactDTOSchema() {
|
||||
get contactDTOSchema(): ValidationChain[] {
|
||||
return [
|
||||
check('first_name').optional().trim().escape(),
|
||||
check('last_name').optional().trim().escape(),
|
||||
@@ -39,8 +38,9 @@ export default class ContactsController extends BaseController {
|
||||
|
||||
/**
|
||||
* Contact new DTO schema.
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get contactNewDTOSchema() {
|
||||
get contactNewDTOSchema(): ValidationChain[] {
|
||||
return [
|
||||
check('balance').optional().isNumeric().toInt(),
|
||||
];
|
||||
@@ -48,20 +48,27 @@ export default class ContactsController extends BaseController {
|
||||
|
||||
/**
|
||||
* Contact edit DTO schema.
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get contactEditDTOSchema() {
|
||||
get contactEditDTOSchema(): ValidationChain[] {
|
||||
return [
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
get specificContactSchema() {
|
||||
/**
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get specificContactSchema(): ValidationChain[] {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
get bulkContactsSchema() {
|
||||
/**
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get bulkContactsSchema(): ValidationChain[] {
|
||||
return [
|
||||
query('ids').isArray({ min: 2 }),
|
||||
query('ids.*').isNumeric().toInt(),
|
||||
|
||||
@@ -19,36 +19,36 @@ export default class CustomersController extends ContactsController {
|
||||
const router = Router();
|
||||
|
||||
router.post('/', [
|
||||
...this.contactDTOSchema,
|
||||
...this.contactNewDTOSchema,
|
||||
...this.customerDTOSchema,
|
||||
],
|
||||
...this.contactDTOSchema,
|
||||
...this.contactNewDTOSchema,
|
||||
...this.customerDTOSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newCustomer.bind(this))
|
||||
);
|
||||
router.post('/:id', [
|
||||
...this.contactDTOSchema,
|
||||
...this.contactEditDTOSchema,
|
||||
...this.customerDTOSchema,
|
||||
],
|
||||
...this.contactDTOSchema,
|
||||
...this.contactEditDTOSchema,
|
||||
...this.customerDTOSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editCustomer.bind(this))
|
||||
);
|
||||
router.delete('/:id', [
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteCustomer.bind(this))
|
||||
);
|
||||
router.delete('/', [
|
||||
...this.bulkContactsSchema,
|
||||
],
|
||||
...this.bulkContactsSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteBulkCustomers.bind(this))
|
||||
);
|
||||
router.get('/:id', [
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getCustomer.bind(this))
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response, Router, NextFunction } from 'express';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { check, query } from 'express-validator';
|
||||
import { check, query, ValidationChain } from 'express-validator';
|
||||
import ContactsController from 'api/controllers/Contacts/Contacts';
|
||||
import VendorsService from 'services/Contacts/VendorsService';
|
||||
import { ServiceError } from 'exceptions';
|
||||
@@ -19,42 +19,42 @@ export default class VendorsController extends ContactsController {
|
||||
const router = Router();
|
||||
|
||||
router.post('/', [
|
||||
...this.contactDTOSchema,
|
||||
...this.contactNewDTOSchema,
|
||||
...this.vendorDTOSchema,
|
||||
],
|
||||
...this.contactDTOSchema,
|
||||
...this.contactNewDTOSchema,
|
||||
...this.vendorDTOSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newVendor.bind(this))
|
||||
);
|
||||
router.post('/:id', [
|
||||
...this.contactDTOSchema,
|
||||
...this.contactEditDTOSchema,
|
||||
...this.vendorDTOSchema,
|
||||
],
|
||||
...this.contactDTOSchema,
|
||||
...this.contactEditDTOSchema,
|
||||
...this.vendorDTOSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editVendor.bind(this))
|
||||
);
|
||||
router.delete('/:id', [
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteVendor.bind(this))
|
||||
);
|
||||
router.delete('/', [
|
||||
...this.bulkContactsSchema,
|
||||
],
|
||||
...this.bulkContactsSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteBulkVendors.bind(this))
|
||||
);
|
||||
router.get('/:id', [
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
...this.specificContactSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getVendor.bind(this))
|
||||
);
|
||||
router.get('/', [
|
||||
...this.vendorsListSchema,
|
||||
],
|
||||
...this.vendorsListSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getVendorsList.bind(this)),
|
||||
);
|
||||
@@ -63,8 +63,9 @@ export default class VendorsController extends ContactsController {
|
||||
|
||||
/**
|
||||
* Vendor DTO schema.
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get vendorDTOSchema() {
|
||||
get vendorDTOSchema(): ValidationChain[] {
|
||||
return [
|
||||
check('opening_balance').optional().isNumeric().toInt(),
|
||||
];
|
||||
@@ -72,6 +73,7 @@ export default class VendorsController extends ContactsController {
|
||||
|
||||
/**
|
||||
* Vendors datatable list validation schema.
|
||||
* @returns {ValidationChain[]}
|
||||
*/
|
||||
get vendorsListSchema() {
|
||||
return [
|
||||
@@ -231,7 +233,7 @@ export default class VendorsController extends ContactsController {
|
||||
const vendors = await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
|
||||
return res.status(200).send({ vendors });
|
||||
} catch (error) {
|
||||
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
import express from 'express';
|
||||
import { check, param, validationResult } from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/',
|
||||
this.all.validation,
|
||||
asyncMiddleware(this.all.handler));
|
||||
|
||||
router.post('/',
|
||||
this.newCurrency.validation,
|
||||
asyncMiddleware(this.newCurrency.handler));
|
||||
|
||||
router.post('/:id',
|
||||
this.editCurrency.validation,
|
||||
asyncMiddleware(this.editCurrency.handler));
|
||||
|
||||
router.delete('/:currency_code',
|
||||
this.deleteCurrecy.validation,
|
||||
asyncMiddleware(this.deleteCurrecy.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve all registered currency details.
|
||||
*/
|
||||
all: {
|
||||
validation: [],
|
||||
async handler(req, res) {
|
||||
const { Currency } = req.models;
|
||||
const currencies = await Currency.query();
|
||||
|
||||
return res.status(200).send({
|
||||
currencies: [
|
||||
...currencies,
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
newCurrency: {
|
||||
validation: [
|
||||
check('currency_name').exists().trim().escape(),
|
||||
check('currency_code').exists().trim().escape(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const form = { ...req.body };
|
||||
const { Currency } = req.models;
|
||||
|
||||
const foundCurrency = await Currency.query()
|
||||
.where('currency_code', form.currency_code);
|
||||
|
||||
if (foundCurrency.length > 0) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'CURRENCY.CODE.ALREADY.EXISTS', code: 100 }],
|
||||
});
|
||||
}
|
||||
await Currency.query()
|
||||
.insert({ ...form });
|
||||
|
||||
return res.status(200).send({
|
||||
currency: { ...form },
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
deleteCurrecy: {
|
||||
validation: [
|
||||
param('currency_code').exists().trim().escape(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { Currency } = req.models;
|
||||
const { currency_code: currencyCode } = req.params;
|
||||
|
||||
await Currency.query()
|
||||
.where('currency_code', currencyCode)
|
||||
.delete();
|
||||
|
||||
return res.status(200).send({ currency_code: currencyCode });
|
||||
},
|
||||
},
|
||||
|
||||
editCurrency: {
|
||||
validation: [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
check('currency_name').exists().trim().escape(),
|
||||
check('currency_code').exists().trim().escape(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const form = { ...req.body };
|
||||
const { id } = req.params;
|
||||
const { Currency } = req.models;
|
||||
|
||||
const foundCurrency = await Currency.query()
|
||||
.where('currency_code', form.currency_code).whereNot('id', id);
|
||||
|
||||
if (foundCurrency.length > 0) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'CURRENCY.CODE.ALREADY.EXISTS', code: 100 }],
|
||||
});
|
||||
}
|
||||
await Currency.query().where('id', id).update({ ...form });
|
||||
|
||||
return res.status(200).send({ currency: { ...form } });
|
||||
},
|
||||
},
|
||||
};
|
||||
180
server/src/api/controllers/Currencies.ts
Normal file
180
server/src/api/controllers/Currencies.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query, ValidationChain } from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BaseController from './BaseController';
|
||||
import CurrenciesService from 'services/Currencies/CurrenciesService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from 'exceptions';
|
||||
|
||||
@Service()
|
||||
export default class CurrenciesController extends BaseController {
|
||||
@Inject()
|
||||
currenciesService: CurrenciesService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get('/', [
|
||||
...this.listSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.all.bind(this))
|
||||
);
|
||||
router.post('/', [
|
||||
...this.currencyDTOSchemaValidation,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newCurrency.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.post('/:id', [
|
||||
...this.currencyIdParamSchema,
|
||||
...this.currencyEditDTOSchemaValidation
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editCurrency.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
router.delete('/:currency_code', [
|
||||
...this.currencyParamSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteCurrency.bind(this)),
|
||||
this.handlerServiceError,
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
get currencyDTOSchemaValidation(): ValidationChain[] {
|
||||
return [
|
||||
check('currency_name').exists().trim().escape(),
|
||||
check('currency_code').exists().trim().escape(),
|
||||
];
|
||||
}
|
||||
|
||||
get currencyEditDTOSchemaValidation(): ValidationChain[] {
|
||||
return [
|
||||
check('currency_name').exists().trim().escape(),
|
||||
];
|
||||
}
|
||||
|
||||
get currencyIdParamSchema(): ValidationChain[] {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
get currencyParamSchema(): ValidationChain[] {
|
||||
return [
|
||||
param('currency_code').exists().trim().escape(),
|
||||
];
|
||||
}
|
||||
|
||||
get listSchema(): ValidationChain[] {
|
||||
return [
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all registered currency details.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async all(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const currencies = await this.currenciesService.listCurrencies(tenantId);
|
||||
return res.status(200).send({ currencies: [ ...currencies, ] });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new currency on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async newCurrency(req: Request, res: Response, next: Function) {
|
||||
const { tenantId } = req;
|
||||
const currencyDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.currenciesService.newCurrency(tenantId, currencyDTO);
|
||||
|
||||
return res.status(200).send({
|
||||
currency_code: currencyDTO.currencyCode,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits details of the given currency.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async deleteCurrency(req: Request, res: Response, next: Function) {
|
||||
const { tenantId } = req;
|
||||
const { currency_code: currencyCode } = req.params;
|
||||
|
||||
try {
|
||||
await this.currenciesService.deleteCurrency(tenantId, currencyCode);
|
||||
return res.status(200).send({ currency_code: currencyCode });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the currency.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async editCurrency(req: Request, res: Response, next: Function) {
|
||||
const { tenantId } = req;
|
||||
const { id: currencyId } = req.params;
|
||||
const { body: editCurrencyDTO } = req;
|
||||
|
||||
try {
|
||||
const currency = await this.currenciesService.editCurrency(tenantId, currencyId, editCurrencyDTO);
|
||||
return res.status(200).send({ currency_code: currency.currencyCode });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles currencies service error.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handlerServiceError(error, req, res, next) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'currency_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'CURRENCY_NOT_FOUND', code: 100, }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'currency_code_exists') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'CURRENCY_CODE_EXISTS', code: 200, }],
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
@@ -1,209 +0,0 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
check,
|
||||
param,
|
||||
query,
|
||||
validationResult,
|
||||
} from 'express-validator';
|
||||
import moment from 'moment';
|
||||
import { difference } from 'lodash';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/',
|
||||
this.exchangeRates.validation,
|
||||
asyncMiddleware(this.exchangeRates.handler));
|
||||
|
||||
router.post('/',
|
||||
this.addExchangeRate.validation,
|
||||
asyncMiddleware(this.addExchangeRate.handler));
|
||||
|
||||
router.post('/:id',
|
||||
this.editExchangeRate.validation,
|
||||
asyncMiddleware(this.editExchangeRate.handler));
|
||||
|
||||
router.delete('/bulk',
|
||||
this.bulkDeleteExchangeRates.validation,
|
||||
asyncMiddleware(this.bulkDeleteExchangeRates.handler));
|
||||
|
||||
router.delete('/:id',
|
||||
this.deleteExchangeRate.validation,
|
||||
asyncMiddleware(this.deleteExchangeRate.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve exchange rates.
|
||||
*/
|
||||
exchangeRates: {
|
||||
validation: [
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const filter = {
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
...req.query,
|
||||
};
|
||||
const { ExchangeRate } = req.models;
|
||||
const exchangeRates = await ExchangeRate.query()
|
||||
.pagination(filter.page - 1, filter.page_size);
|
||||
|
||||
return res.status(200).send({ exchange_rates: exchangeRates });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new exchange rate on the given date.
|
||||
*/
|
||||
addExchangeRate: {
|
||||
validation: [
|
||||
check('exchange_rate').exists().isNumeric().toFloat(),
|
||||
check('currency_code').exists().trim().escape(),
|
||||
check('date').exists().isISO8601(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { ExchangeRate } = req.models;
|
||||
const form = { ...req.body };
|
||||
const foundExchangeRate = await ExchangeRate.query()
|
||||
.where('currency_code', form.currency_code)
|
||||
.where('date', form.date);
|
||||
|
||||
if (foundExchangeRate.length > 0) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'EXCHANGE.RATE.DATE.PERIOD.DEFINED', code: 200 }],
|
||||
});
|
||||
}
|
||||
await ExchangeRate.query().insert({
|
||||
...form,
|
||||
date: moment(form.date).format('YYYY-MM-DD'),
|
||||
});
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Edit the given exchange rate.
|
||||
*/
|
||||
editExchangeRate: {
|
||||
validation: [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
check('exchange_rate').exists().isNumeric().toFloat(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { id } = req.params;
|
||||
const form = { ...req.body };
|
||||
const { ExchangeRate } = req.models;
|
||||
|
||||
const foundExchangeRate = await ExchangeRate.query()
|
||||
.where('id', id);
|
||||
|
||||
if (!foundExchangeRate.length) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
await ExchangeRate.query()
|
||||
.where('id', id)
|
||||
.update({ ...form });
|
||||
|
||||
return res.status(200).send({ id });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the given exchange rate from the storage.
|
||||
*/
|
||||
deleteExchangeRate: {
|
||||
validation: [
|
||||
param('id').isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const { id } = req.params;
|
||||
const { ExchangeRate } = req.models;
|
||||
const foundExchangeRate = await ExchangeRate.query().where('id', id);
|
||||
|
||||
if (!foundExchangeRate.length) {
|
||||
return res.status(404).send({
|
||||
errors: [{ type: 'EXCHANGE.RATE.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
await ExchangeRate.query().where('id', id).delete();
|
||||
|
||||
return res.status(200).send({ id });
|
||||
},
|
||||
},
|
||||
|
||||
bulkDeleteExchangeRates: {
|
||||
validation: [
|
||||
query('ids').isArray({ min: 2 }),
|
||||
query('ids.*').isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const filter = {
|
||||
ids: [],
|
||||
...req.query,
|
||||
};
|
||||
const { ExchangeRate } = req.models;
|
||||
|
||||
const exchangeRates = await ExchangeRate.query().whereIn('id', filter.ids);
|
||||
const exchangeRatesIds = exchangeRates.map((category) => category.id);
|
||||
const notFoundExRates = difference(filter.ids, exchangeRatesIds);
|
||||
|
||||
if (notFoundExRates.length > 0) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 200, ids: notFoundExRates }],
|
||||
});
|
||||
}
|
||||
await ExchangeRate.query().whereIn('id', exchangeRatesIds).delete();
|
||||
|
||||
return res.status(200).send({ ids: exchangeRatesIds });
|
||||
},
|
||||
},
|
||||
}
|
||||
217
server/src/api/controllers/ExchangeRates.ts
Normal file
217
server/src/api/controllers/ExchangeRates.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
check,
|
||||
param,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BaseController from './BaseController';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import ExchangeRatesService from 'services/ExchangeRates/ExchangeRatesService';
|
||||
|
||||
@Service()
|
||||
export default class ExchangeRatesController extends BaseController {
|
||||
|
||||
@Inject()
|
||||
exchangeRatesService: ExchangeRatesService;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get('/', [
|
||||
...this.exchangeRatesListSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.exchangeRates.bind(this)),
|
||||
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('/bulk', [
|
||||
...this.exchangeRatesIdsSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.bulkDeleteExchangeRates.bind(this)),
|
||||
this.handleServiceError,
|
||||
);
|
||||
router.delete('/:id', [
|
||||
...this.exchangeRateIdSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteExchangeRate.bind(this)),
|
||||
this.handleServiceError,
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
get exchangeRatesListSchema() {
|
||||
return [
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
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) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
...req.query,
|
||||
};
|
||||
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 {
|
||||
const exchangeRate = await this.exchangeRatesService.newExchangeRate(tenantId, exchangeRateDTO)
|
||||
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 exchangeRateDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const exchangeRate = await this.exchangeRatesService.newExchangeRate(tenantId, exchangeRateDTO)
|
||||
return res.status(200).send({ id: exchangeRate.id });
|
||||
} 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) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given exchange rates in bulk.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async bulkDeleteExchangeRates(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { ids: exchangeRateIds } = req.query;
|
||||
|
||||
try {
|
||||
await this.exchangeRatesService.deleteBulkExchangeRates(tenantId, exchangeRateIds);
|
||||
return res.status(200).send();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle service errors.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
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') {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'EXCHANGE.RATES.IS.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'EXCHANGE_RATE_PERIOD_EXISTS') {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'EXCHANGE.RATE.PERIOD.EXISTS', code: 300 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,7 +211,12 @@ export default class ExpensesController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Publishes the given expenses in bulk.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async bulkPublishExpenses(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ export default class InviteUsersController extends BaseController {
|
||||
const router = Router();
|
||||
|
||||
router.post('/send', [
|
||||
body('email').exists().trim().escape(),
|
||||
],
|
||||
body('email').exists().trim().escape(),
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.sendInvite.bind(this)),
|
||||
);
|
||||
@@ -117,9 +117,7 @@ export default class InviteUsersController extends BaseController {
|
||||
message: 'User invite has been accepted successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
if (error instanceof ServiceError) {
|
||||
|
||||
if (error.errorType === 'phone_number_exists') {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'PHONE_NUMBER.EXISTS' }],
|
||||
|
||||
@@ -60,10 +60,9 @@ export default () => {
|
||||
|
||||
dashboard.use('/users', Container.get(Users).router());
|
||||
dashboard.use('/invite', Container.get(InviteUsers).authRouter());
|
||||
dashboard.use('/currencies', Currencies.router());
|
||||
dashboard.use('/currencies', Container.get(Currencies).router());
|
||||
dashboard.use('/accounts', Container.get(Accounts).router());
|
||||
dashboard.use('/account_types', Container.get(AccountTypes).router());
|
||||
// dashboard.use('/accounting', Accounting.router());
|
||||
dashboard.use('/manual-journals', Container.get(ManualJournals).router());
|
||||
dashboard.use('/views', Views.router());
|
||||
dashboard.use('/items', Container.get(Items).router());
|
||||
@@ -76,7 +75,7 @@ export default () => {
|
||||
dashboard.use('/vendors', Container.get(Vendors).router());
|
||||
dashboard.use('/purchases', Purchases.router());
|
||||
dashboard.use('/resources', Resources.router());
|
||||
dashboard.use('/exchange_rates', ExchangeRates.router());
|
||||
dashboard.use('/exchange_rates', Container.get(ExchangeRates).router());
|
||||
dashboard.use('/media', Media.router())
|
||||
|
||||
app.use('/', dashboard);
|
||||
|
||||
24
server/src/interfaces/Currency.ts
Normal file
24
server/src/interfaces/Currency.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
export interface ICurrencyDTO {
|
||||
currencyName: string,
|
||||
currencyCode: string,
|
||||
};
|
||||
export interface ICurrencyEditDTO {
|
||||
currencyName: string,
|
||||
}
|
||||
export interface ICurrency {
|
||||
id: number,
|
||||
currencyName: string,
|
||||
currencyCode: string,
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
};
|
||||
|
||||
export interface ICurrenciesService {
|
||||
newCurrency(tenantId: number, currencyDTO: ICurrencyDTO): Promise<void>;
|
||||
editCurrency(tenantId: number, currencyId: number, editCurrencyDTO: ICurrencyEditDTO): Promise<void>;
|
||||
|
||||
deleteCurrency(tenantId: number, currencyCode: string): Promise<void>;
|
||||
listCurrencies(tenantId: number): Promise<ICurrency[]>;
|
||||
}
|
||||
32
server/src/interfaces/ExchangeRate.ts
Normal file
32
server/src/interfaces/ExchangeRate.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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>;
|
||||
};
|
||||
@@ -2,7 +2,6 @@ import { IDynamicListFilterDTO } from "./DynamicFilter";
|
||||
import { IJournalEntry } from "./Journal";
|
||||
import { ISystemUser } from "./User";
|
||||
|
||||
|
||||
export interface IManualJournal {
|
||||
id: number,
|
||||
date: Date|string,
|
||||
|
||||
@@ -20,4 +20,6 @@ export * from './Contact';
|
||||
export * from './Expenses';
|
||||
export * from './Tenancy';
|
||||
export * from './View';
|
||||
export * from './ManualJournal';
|
||||
export * from './ManualJournal';
|
||||
export * from './Currency';
|
||||
export * from './ExchangeRate';
|
||||
@@ -11,7 +11,7 @@ export default class Currency extends TenantModel {
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class ExchangeRate extends TenantModel {
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export default class AuthenticationService {
|
||||
* @param {string} password - Password.
|
||||
* @return {Promise<{user: IUser, token: string}>}
|
||||
*/
|
||||
async signIn(emailOrPhone: string, password: string): Promise<{user: IUser, token: string, tenant: ITenant }> {
|
||||
async signIn(emailOrPhone: string, password: string): Promise<{user: ISystemUser, token: string, tenant: ITenant }> {
|
||||
this.logger.info('[login] Someone trying to login.', { emailOrPhone, password });
|
||||
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Inject, Service } from 'typedi';
|
||||
import { difference, upperFirst } from 'lodash';
|
||||
import { ServiceError } from "exceptions";
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import {
|
||||
import {
|
||||
IContact,
|
||||
IContactNewDTO,
|
||||
IContactEditDTO,
|
||||
|
||||
152
server/src/services/Currencies/CurrenciesService.ts
Normal file
152
server/src/services/Currencies/CurrenciesService.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { Inject, Container, Service } from 'typedi';
|
||||
import {
|
||||
ICurrencyEditDTO,
|
||||
ICurrencyDTO,
|
||||
ICurrenciesService,
|
||||
ICurrency
|
||||
} from 'interfaces';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
|
||||
const ERRORS = {
|
||||
CURRENCY_NOT_FOUND: 'currency_not_found',
|
||||
CURRENCY_CODE_EXISTS: 'currency_code_exists'
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class CurrenciesService implements ICurrenciesService {
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface;
|
||||
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve currency by given currency code or throw not found error.
|
||||
* @param {number} tenantId
|
||||
* @param {string} currencyCode
|
||||
* @param {number} currencyId
|
||||
*/
|
||||
private async validateCurrencyCodeUniquiness(tenantId: number, currencyCode: string, currencyId?: number) {
|
||||
const { Currency } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[currencies] trying to validate currency code existance.', { tenantId, currencyCode });
|
||||
const foundCurrency = await Currency.query().onBuild((query) => {
|
||||
query.findOne('currency_code', currencyCode);
|
||||
|
||||
if (currencyId) {
|
||||
query.whereNot('id', currencyId)
|
||||
}
|
||||
});
|
||||
if (foundCurrency) {
|
||||
this.logger.info('[currencies] the currency code already exists.', { tenantId, currencyCode });
|
||||
throw new ServiceError(ERRORS.CURRENCY_CODE_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve currency by the given currency code or throw service error.
|
||||
* @param {number} tenantId
|
||||
* @param {string} currencyCode
|
||||
*/
|
||||
private async getCurrencyByCodeOrThrowError(tenantId: number, currencyCode: string) {
|
||||
const { Currency } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[currencies] trying to validate currency code existance.', { tenantId, currencyCode });
|
||||
const foundCurrency = await Currency.query().findOne('currency_code', currencyCode);
|
||||
|
||||
if (!foundCurrency) {
|
||||
this.logger.info('[currencies] the given currency code not exists.', { tenantId, currencyCode });
|
||||
throw new ServiceError(ERRORS.CURRENCY_NOT_FOUND);
|
||||
}
|
||||
return foundCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve currency by given id or throw not found error.
|
||||
* @param {number} tenantId
|
||||
* @param {number} currencyId
|
||||
*/
|
||||
private async getCurrencyIdOrThrowError(tenantId: number, currencyId: number) {
|
||||
const { Currency } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[currencies] trying to validate currency by id existance.', { tenantId, currencyId });
|
||||
const foundCurrency = await Currency.query().findOne('id', currencyId);
|
||||
|
||||
if (!foundCurrency) {
|
||||
this.logger.info('[currencies] the currency code not found.', { tenantId, currencyId });
|
||||
throw new ServiceError(ERRORS.CURRENCY_NOT_FOUND);
|
||||
}
|
||||
return foundCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new currency.
|
||||
* @param {number} tenantId
|
||||
* @param {ICurrencyDTO} currencyDTO
|
||||
*/
|
||||
public async newCurrency(tenantId: number, currencyDTO: ICurrencyDTO) {
|
||||
const { Currency } = this.tenancy.models(tenantId);
|
||||
this.logger.info('[currencies] try to insert new currency.', { tenantId, currencyDTO });
|
||||
|
||||
await this.validateCurrencyCodeUniquiness(tenantId, currencyDTO.currencyCode);
|
||||
|
||||
await Currency.query().insert({ ...currencyDTO });
|
||||
this.logger.info('[currencies] the currency inserted successfully.', { tenantId, currencyDTO });
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details of the given currency.
|
||||
* @param {number} tenantId
|
||||
* @param {number} currencyId
|
||||
* @param {ICurrencyDTO} currencyDTO
|
||||
*/
|
||||
public async editCurrency(tenantId: number, currencyId: number, currencyDTO: ICurrencyEditDTO): Promise<ICurrency> {
|
||||
const { Currency } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[currencies] try to edit currency.', { tenantId, currencyId, currencyDTO });
|
||||
await this.getCurrencyIdOrThrowError(tenantId, currencyId);
|
||||
|
||||
const currency = await Currency.query().patchAndFetchById(currencyId, { ...currencyDTO });
|
||||
this.logger.info('[currencies] the currency edited successfully.', { tenantId, currencyDTO });
|
||||
|
||||
return currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given currency code.
|
||||
* @param {number} tenantId
|
||||
* @param {string} currencyCode
|
||||
* @return {Promise<}
|
||||
*/
|
||||
public async deleteCurrency(tenantId: number, currencyCode: string): Promise<void> {
|
||||
const { Currency } = this.tenancy.models(tenantId);
|
||||
this.logger.info('[currencies] trying to delete the given currency.', { tenantId, currencyCode });
|
||||
|
||||
await this.getCurrencyByCodeOrThrowError(tenantId, currencyCode);
|
||||
|
||||
await Currency.query().where('currency_code', currencyCode).delete();
|
||||
this.logger.info('[currencies] the currency deleted successfully.', { tenantId, currencyCode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Listing currencies.
|
||||
* @param {number} tenantId
|
||||
* @return {Promise<ICurrency[]>}
|
||||
*/
|
||||
public async listCurrencies(tenantId: number): Promise<ICurrency[]> {
|
||||
const { Currency } = this.tenancy.models(tenantId);
|
||||
|
||||
const currencies = await Currency.query().onBuild((query) => {
|
||||
query.orderBy('createdAt', 'ASC');
|
||||
});
|
||||
return currencies;
|
||||
}
|
||||
}
|
||||
166
server/src/services/ExchangeRates/ExchangeRatesService.ts
Normal file
166
server/src/services/ExchangeRates/ExchangeRatesService.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import moment from 'moment';
|
||||
import { difference } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { ServiceError } from 'exceptions';
|
||||
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',
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class ExchangeRatesService implements IExchangeRatesService {
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface;
|
||||
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Creates a new exchange rate.
|
||||
* @param {number} tenantId
|
||||
* @param {IExchangeRateDTO} exchangeRateDTO
|
||||
* @returns {Promise<IExchangeRate>}
|
||||
*/
|
||||
public async newExchangeRate(tenantId: number, exchangeRateDTO: IExchangeRateDTO): Promise<IExchangeRate> {
|
||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[exchange_rates] trying to insert new exchange rate.', { tenantId, 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 exchangeRates = await ExchangeRate.query()
|
||||
.pagination(exchangeRateFilter.page - 1, exchangeRateFilter.pageSize);
|
||||
|
||||
return exchangeRates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes exchange rates in bulk.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} exchangeRatesIds
|
||||
*/
|
||||
public async deleteBulkExchangeRates(tenantId: number, exchangeRatesIds: number[]): Promise<void> {
|
||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[exchange_rates] trying delete in bulk.', { tenantId, exchangeRatesIds });
|
||||
await this.validateExchangeRatesIdsExistance(tenantId, exchangeRatesIds);
|
||||
|
||||
await ExchangeRate.query().whereIn('id', exchangeRatesIds).delete();
|
||||
this.logger.info('[exchange_rates] deleted successfully.', { tenantId, exchangeRatesIds });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates exchange rates ids existance.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number[]} exchangeRatesIds - Exchange rates ids.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async validateExchangeRatesIdsExistance(tenantId: number, exchangeRatesIds: number[]): Promise<void> {
|
||||
const { ExchangeRate } = this.tenancy.models(tenantId);
|
||||
|
||||
const storedExchangeRates = await ExchangeRate.query().whereIn('id', exchangeRatesIds);
|
||||
const storedExchangeRatesIds = storedExchangeRates.map((category) => category.id);
|
||||
const notFoundExRates = difference(exchangeRatesIds, storedExchangeRatesIds);
|
||||
|
||||
if (notFoundExRates.length > 0) {
|
||||
throw new ServiceError(ERRORS.NOT_FOUND_EXCHANGE_RATES);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ export default class TenantsManagerService implements ITenantManager{
|
||||
|
||||
/**
|
||||
* Throws error if the tenant database is not built yut.
|
||||
* @param tenant
|
||||
* @param {ITenant} tenant
|
||||
*/
|
||||
private throwErrorIfTenantNotBuilt(tenant: ITenant) {
|
||||
if (!tenant.initializedAt) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Authentication service.
|
||||
*/
|
||||
auth: {
|
||||
login: 'onLogin',
|
||||
register: 'onRegister',
|
||||
@@ -8,23 +11,35 @@ export default {
|
||||
resetPassword: 'onResetPassword',
|
||||
},
|
||||
|
||||
/**
|
||||
* Invite users service.
|
||||
*/
|
||||
inviteUser: {
|
||||
acceptInvite: 'onUserAcceptInvite',
|
||||
sendInvite: 'onUserSendInvite',
|
||||
checkInvite: 'onUserCheckInvite'
|
||||
},
|
||||
|
||||
/**
|
||||
* Organization managment service.
|
||||
*/
|
||||
organization: {
|
||||
build: 'onOrganizationBuild',
|
||||
seeded: 'onOrganizationSeeded',
|
||||
},
|
||||
|
||||
/**
|
||||
* Tenants managment service.
|
||||
*/
|
||||
tenantManager: {
|
||||
databaseCreated: 'onDatabaseCreated',
|
||||
tenantMigrated: 'onTenantMigrated',
|
||||
tenantSeeded: 'onTenantSeeded',
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual journals service.
|
||||
*/
|
||||
manualJournals: {
|
||||
onCreated: 'onManualJournalCreated',
|
||||
onEdited: 'onManualJournalEdited',
|
||||
|
||||
@@ -10,7 +10,7 @@ export default class SubscriptionRepository extends SystemRepository{
|
||||
/**
|
||||
* Retrieve subscription from a given slug in specific tenant.
|
||||
* @param {string} slug
|
||||
* @param {number] tenantId
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
getBySlugInTenant(slug: string, tenantId: number) {
|
||||
const key = `subscription.slug.${slug}.tenant.${tenantId}`;
|
||||
|
||||
Reference in New Issue
Block a user