mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
fix: system repositories.
This commit is contained in:
@@ -10,13 +10,13 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import { DATATYPES_LENGTH } from 'data/DataTypes';
|
||||
|
||||
@Service()
|
||||
export default class AccountsController extends BaseController{
|
||||
export default class AccountsController extends BaseController {
|
||||
@Inject()
|
||||
accountsService: AccountsService;
|
||||
|
||||
@Inject()
|
||||
dynamicListService: DynamicListingService;
|
||||
|
||||
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
@@ -24,84 +24,72 @@ export default class AccountsController extends BaseController{
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/bulk/:type(activate|inactivate)', [
|
||||
...this.bulkSelectIdsQuerySchema
|
||||
],
|
||||
'/bulk/:type(activate|inactivate)',
|
||||
[...this.bulkSelectIdsQuerySchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.bulkToggleActivateAccounts.bind(this))
|
||||
);
|
||||
router.post(
|
||||
'/:id/activate', [
|
||||
...this.accountParamSchema,
|
||||
],
|
||||
'/:id/activate',
|
||||
[...this.accountParamSchema],
|
||||
asyncMiddleware(this.activateAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/inactivate', [
|
||||
...this.accountParamSchema,
|
||||
],
|
||||
'/:id/inactivate',
|
||||
[...this.accountParamSchema],
|
||||
asyncMiddleware(this.inactivateAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/close', [
|
||||
...this.accountParamSchema,
|
||||
...this.closingAccountSchema,
|
||||
],
|
||||
'/:id/close',
|
||||
[...this.accountParamSchema, ...this.closingAccountSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.closeAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
)
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id', [
|
||||
...this.accountDTOSchema,
|
||||
...this.accountParamSchema,
|
||||
],
|
||||
'/:id',
|
||||
[...this.accountDTOSchema, ...this.accountParamSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/', [
|
||||
...this.accountDTOSchema,
|
||||
],
|
||||
'/',
|
||||
[...this.accountDTOSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:id', [
|
||||
...this.accountParamSchema,
|
||||
],
|
||||
'/:id',
|
||||
[...this.accountParamSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/', [
|
||||
...this.accountsListSchema,
|
||||
],
|
||||
'/',
|
||||
[...this.accountsListSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getAccountsList.bind(this)),
|
||||
this.dynamicListService.handlerErrorsToResponse,
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.delete(
|
||||
'/', [
|
||||
...this.bulkSelectIdsQuerySchema,
|
||||
],
|
||||
'/',
|
||||
[...this.bulkSelectIdsQuerySchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteBulkAccounts.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.delete(
|
||||
'/:id', [
|
||||
...this.accountParamSchema
|
||||
],
|
||||
'/:id',
|
||||
[...this.accountParamSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteAccount.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
return router;
|
||||
}
|
||||
@@ -138,9 +126,7 @@ export default class AccountsController extends BaseController{
|
||||
}
|
||||
|
||||
get accountParamSchema() {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt()
|
||||
];
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
get accountsListSchema() {
|
||||
@@ -164,21 +150,24 @@ export default class AccountsController extends BaseController{
|
||||
return [
|
||||
check('to_account_id').exists().isNumeric().toInt(),
|
||||
check('delete_after_closing').exists().isBoolean(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new account.
|
||||
* @param {Request} req -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next -
|
||||
* @param {NextFunction} next -
|
||||
*/
|
||||
async newAccount(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const accountDTO: IAccountDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const account = await this.accountsService.newAccount(tenantId, accountDTO);
|
||||
const account = await this.accountsService.newAccount(
|
||||
tenantId,
|
||||
accountDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({ id: account.id });
|
||||
} catch (error) {
|
||||
@@ -188,7 +177,7 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Edit account details.
|
||||
* @param {Request} req
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
@@ -198,7 +187,11 @@ export default class AccountsController extends BaseController{
|
||||
const accountDTO: IAccountDTO = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const account = await this.accountsService.editAccount(tenantId, accountId, accountDTO);
|
||||
const account = await this.accountsService.editAccount(
|
||||
tenantId,
|
||||
accountId,
|
||||
accountDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: account.id,
|
||||
@@ -211,18 +204,20 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Get details of the given account.
|
||||
* @param {Request} req
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
async getAccount(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { id: accountId } = req.params;
|
||||
|
||||
try {
|
||||
const account = await this.accountsService.getAccount(tenantId, accountId);
|
||||
return res.status(200).send({ account });
|
||||
|
||||
try {
|
||||
const account = await this.accountsService.getAccount(
|
||||
tenantId,
|
||||
accountId
|
||||
);
|
||||
return res.status(200).send({ account });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -230,7 +225,7 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Delete the given account.
|
||||
* @param {Request} req
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @return {Response}
|
||||
*/
|
||||
@@ -252,11 +247,11 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Activate the given account.
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @return {Response}
|
||||
*/
|
||||
async activateAccount(req: Request, res: Response, next: Function){
|
||||
async activateAccount(req: Request, res: Response, next: Function) {
|
||||
const { id: accountId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
@@ -270,18 +265,17 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Inactive the given account.
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @return {Response}
|
||||
*/
|
||||
async inactivateAccount(req: Request, res: Response, next: Function){
|
||||
async inactivateAccount(req: Request, res: Response, next: Function) {
|
||||
const { id: accountId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
await this.accountsService.activateAccount(tenantId, accountId, false);
|
||||
return res.status(200).send({ id: accountId });
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -289,18 +283,26 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Bulk activate/inactivate accounts.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async bulkToggleActivateAccounts(req: Request, res: Response, next: Function) {
|
||||
async bulkToggleActivateAccounts(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: Function
|
||||
) {
|
||||
const { type } = req.params;
|
||||
const { tenantId } = req;
|
||||
const { tenantId } = req;
|
||||
const { ids: accountsIds } = req.query;
|
||||
|
||||
|
||||
try {
|
||||
const isActive = (type === 'activate' ? true : false);
|
||||
await this.accountsService.activateAccounts(tenantId, accountsIds, isActive);
|
||||
const isActive = type === 'activate' ? true : false;
|
||||
await this.accountsService.activateAccounts(
|
||||
tenantId,
|
||||
accountsIds,
|
||||
isActive
|
||||
);
|
||||
|
||||
const activatedText = isActive ? 'activated' : 'inactivated';
|
||||
|
||||
@@ -315,9 +317,9 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Deletes accounts in bulk.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async deleteBulkAccounts(req: Request, res: Response, next: NextFunction) {
|
||||
const { ids: accountsIds } = req.query;
|
||||
@@ -337,8 +339,8 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Retrieve accounts datatable list.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAccountsList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
@@ -352,11 +354,14 @@ export default class AccountsController extends BaseController{
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
try {
|
||||
const { accounts, filterMeta } = await this.accountsService.getAccountsList(tenantId, filter);
|
||||
const {
|
||||
accounts,
|
||||
filterMeta,
|
||||
} = await this.accountsService.getAccountsList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
accounts,
|
||||
filter_meta: this.transfromToResponse(filterMeta)
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -365,9 +370,9 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Closes the given account.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param next
|
||||
*/
|
||||
async closeAccount(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
@@ -389,72 +394,72 @@ export default class AccountsController extends BaseController{
|
||||
|
||||
/**
|
||||
* Transforms service errors to response.
|
||||
* @param {Error}
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {ServiceError} error
|
||||
*/
|
||||
* @param {Error}
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {ServiceError} error
|
||||
*/
|
||||
catchServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'account_not_found') {
|
||||
return res.boom.notFound(
|
||||
'The given account not found.',
|
||||
{ errors: [{ type: 'ACCOUNT.NOT.FOUND', code: 100 }] }
|
||||
);
|
||||
return res.boom.notFound('The given account not found.', {
|
||||
errors: [{ type: 'ACCOUNT.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'account_name_not_unqiue') {
|
||||
return res.boom.badRequest(
|
||||
'The given account not unique.',
|
||||
{ errors: [{ type: 'ACCOUNT.NAME.NOT.UNIQUE', code: 150 }], }
|
||||
);
|
||||
return res.boom.badRequest('The given account not unique.', {
|
||||
errors: [{ type: 'ACCOUNT.NAME.NOT.UNIQUE', code: 150 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'account_type_not_found') {
|
||||
return res.boom.badRequest(
|
||||
'The given account type not found.',
|
||||
{ errors: [{ type: 'ACCOUNT_TYPE_NOT_FOUND', code: 200 }] }
|
||||
);
|
||||
return res.boom.badRequest('The given account type not found.', {
|
||||
errors: [{ type: 'ACCOUNT_TYPE_NOT_FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'account_type_not_allowed_to_changed') {
|
||||
return res.boom.badRequest(
|
||||
'Not allowed to change account type of the account.',
|
||||
{ errors: [{ type: 'NOT.ALLOWED.TO.CHANGE.ACCOUNT.TYPE', code: 300 }] }
|
||||
{
|
||||
errors: [{ type: 'NOT.ALLOWED.TO.CHANGE.ACCOUNT.TYPE', code: 300 }],
|
||||
}
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'parent_account_not_found') {
|
||||
return res.boom.badRequest(
|
||||
'The parent account not found.',
|
||||
{ errors: [{ type: 'PARENT_ACCOUNT_NOT_FOUND', code: 400 }] },
|
||||
);
|
||||
return res.boom.badRequest('The parent account not found.', {
|
||||
errors: [{ type: 'PARENT_ACCOUNT_NOT_FOUND', code: 400 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'parent_has_different_type') {
|
||||
return res.boom.badRequest(
|
||||
'The parent account has different type.',
|
||||
{ errors: [{ type: 'PARENT.ACCOUNT.HAS.DIFFERENT.ACCOUNT.TYPE', code: 500 }] }
|
||||
);
|
||||
return res.boom.badRequest('The parent account has different type.', {
|
||||
errors: [
|
||||
{ type: 'PARENT.ACCOUNT.HAS.DIFFERENT.ACCOUNT.TYPE', code: 500 },
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'account_code_not_unique') {
|
||||
return res.boom.badRequest(
|
||||
'The given account code is not unique.',
|
||||
{ errors: [{ type: 'NOT_UNIQUE_CODE', code: 600 }] }
|
||||
);
|
||||
return res.boom.badRequest('The given account code is not unique.', {
|
||||
errors: [{ type: 'NOT_UNIQUE_CODE', code: 600 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'account_has_associated_transactions') {
|
||||
return res.boom.badRequest(
|
||||
'You could not delete account has associated transactions.',
|
||||
{ errors: [{ type: 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS', code: 800 }] }
|
||||
{
|
||||
errors: [
|
||||
{ type: 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS', code: 800 },
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
if (error.errorType === 'account_predefined') {
|
||||
return res.boom.badRequest(
|
||||
'You could not delete predefined account',
|
||||
{ errors: [{ type: 'ACCOUNT.PREDEFINED', code: 900 }] }
|
||||
);
|
||||
return res.boom.badRequest('You could not delete predefined account', {
|
||||
errors: [{ type: 'ACCOUNT.PREDEFINED', code: 900 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'accounts_not_found') {
|
||||
return res.boom.notFound(
|
||||
'Some of the given accounts not found.',
|
||||
{ errors: [{ type: 'SOME.ACCOUNTS.NOT_FOUND', code: 1000 }] },
|
||||
);
|
||||
return res.boom.notFound('Some of the given accounts not found.', {
|
||||
errors: [{ type: 'SOME.ACCOUNTS.NOT_FOUND', code: 1000 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'predefined_accounts') {
|
||||
return res.boom.badRequest(
|
||||
@@ -465,10 +470,17 @@ export default class AccountsController extends BaseController{
|
||||
if (error.errorType === 'close_account_and_to_account_not_same_type') {
|
||||
return res.boom.badRequest(
|
||||
'The close account has different root type with to account.',
|
||||
{ errors: [{ type: 'CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE', code: 1200 }] },
|
||||
{
|
||||
errors: [
|
||||
{
|
||||
type: 'CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE',
|
||||
code: 1200,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
next(error)
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
check,
|
||||
param,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import { check, param, query } from 'express-validator';
|
||||
import ItemCategoriesService from 'services/ItemCategories/ItemCategoriesService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
@@ -27,49 +23,51 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.post('/:id', [
|
||||
router.post(
|
||||
'/:id',
|
||||
[
|
||||
...this.categoryValidationSchema,
|
||||
...this.specificCategoryValidationSchema,
|
||||
],
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
this.handlerServiceError
|
||||
);
|
||||
router.post('/', [
|
||||
...this.categoryValidationSchema,
|
||||
],
|
||||
router.post(
|
||||
'/',
|
||||
[...this.categoryValidationSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
this.handlerServiceError
|
||||
);
|
||||
router.delete('/', [
|
||||
...this.categoriesBulkValidationSchema,
|
||||
],
|
||||
router.delete(
|
||||
'/',
|
||||
[...this.categoriesBulkValidationSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.bulkDeleteCategories.bind(this)),
|
||||
this.handlerServiceError,
|
||||
this.handlerServiceError
|
||||
);
|
||||
router.delete('/:id', [
|
||||
...this.specificCategoryValidationSchema
|
||||
],
|
||||
router.delete(
|
||||
'/:id',
|
||||
[...this.specificCategoryValidationSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteItem.bind(this)),
|
||||
this.handlerServiceError,
|
||||
this.handlerServiceError
|
||||
);
|
||||
router.get('/:id', [
|
||||
...this.specificCategoryValidationSchema,
|
||||
],
|
||||
router.get(
|
||||
'/:id',
|
||||
[...this.specificCategoryValidationSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getCategory.bind(this)),
|
||||
this.handlerServiceError,
|
||||
this.handlerServiceError
|
||||
);
|
||||
router.get('/', [
|
||||
...this.categoriesListValidationSchema
|
||||
],
|
||||
router.get(
|
||||
'/',
|
||||
[...this.categoriesListValidationSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getList.bind(this)),
|
||||
this.handlerServiceError,
|
||||
this.dynamicListService.handlerErrorsToResponse,
|
||||
this.dynamicListService.handlerErrorsToResponse
|
||||
);
|
||||
return router;
|
||||
}
|
||||
@@ -102,7 +100,7 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
|
||||
.toInt(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,22 +129,24 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
* Validate specific item category schema.
|
||||
*/
|
||||
get specificCategoryValidationSchema() {
|
||||
return [
|
||||
param('id').exists().toInt(),
|
||||
];
|
||||
}
|
||||
return [param('id').exists().toInt()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new item category.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async newCategory(req: Request, res: Response, next: NextFunction) {
|
||||
const { user, tenantId } = req;
|
||||
const itemCategoryOTD: IItemCategoryOTD = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
const itemCategory = await this.itemCategoriesService.newItemCategory(tenantId, itemCategoryOTD, user);
|
||||
const itemCategory = await this.itemCategoriesService.newItemCategory(
|
||||
tenantId,
|
||||
itemCategoryOTD,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({ id: itemCategory.id });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -155,8 +155,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Edit details of the given category item.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async editCategory(req: Request, res: Response, next: NextFunction) {
|
||||
@@ -165,7 +165,12 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
const itemCategoryOTD: IItemCategoryOTD = this.matchedBodyData(req);
|
||||
|
||||
try {
|
||||
await this.itemCategoriesService.editItemCategory(tenantId, itemCategoryId, itemCategoryOTD, user);
|
||||
await this.itemCategoriesService.editItemCategory(
|
||||
tenantId,
|
||||
itemCategoryId,
|
||||
itemCategoryOTD,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({ id: itemCategoryId });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -174,8 +179,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Delete the give item category.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async deleteItem(req: Request, res: Response, next: NextFunction) {
|
||||
@@ -183,7 +188,11 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
const { tenantId, user } = req;
|
||||
|
||||
try {
|
||||
await this.itemCategoriesService.deleteItemCategory(tenantId, itemCategoryId, user);
|
||||
await this.itemCategoriesService.deleteItemCategory(
|
||||
tenantId,
|
||||
itemCategoryId,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({ id: itemCategoryId });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -192,8 +201,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve the list of items.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async getList(req: Request, res: Response, next: NextFunction) {
|
||||
@@ -206,8 +215,13 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
};
|
||||
|
||||
try {
|
||||
const { itemCategories, filterMeta } = await this.itemCategoriesService.getItemCategoriesList(
|
||||
tenantId, itemCategoriesFilter, user,
|
||||
const {
|
||||
itemCategories,
|
||||
filterMeta,
|
||||
} = await this.itemCategoriesService.getItemCategoriesList(
|
||||
tenantId,
|
||||
itemCategoriesFilter,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({
|
||||
item_categories: itemCategories,
|
||||
@@ -220,8 +234,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve details of the given category.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async getCategory(req: Request, res: Response, next: NextFunction) {
|
||||
@@ -229,7 +243,11 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
const { tenantId, user } = req;
|
||||
|
||||
try {
|
||||
const itemCategory = await this.itemCategoriesService.getItemCategory(tenantId, itemCategoryId, user);
|
||||
const itemCategory = await this.itemCategoriesService.getItemCategory(
|
||||
tenantId,
|
||||
itemCategoryId,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({ category: itemCategory });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -238,8 +256,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Bulk delete the given item categories.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @return {Response}
|
||||
*/
|
||||
async bulkDeleteCategories(req: Request, res: Response, next: NextFunction) {
|
||||
@@ -247,7 +265,11 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
const { tenantId, user } = req;
|
||||
|
||||
try {
|
||||
await this.itemCategoriesService.deleteItemCategories(tenantId, itemCategoriesIds, user);
|
||||
await this.itemCategoriesService.deleteItemCategories(
|
||||
tenantId,
|
||||
itemCategoriesIds,
|
||||
user
|
||||
);
|
||||
return res.status(200).send({ ids: itemCategoriesIds });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -256,12 +278,17 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Handles service error.
|
||||
* @param {Error} error
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next
|
||||
* @param {Error} error
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
handlerServiceError(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||
handlerServiceError(
|
||||
error: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'CATEGORY_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
@@ -295,7 +322,7 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
}
|
||||
if (error.errorType === 'INVENTORY_ACCOUNT_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.FOUND', code: 200}],
|
||||
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'INVENTORY_ACCOUNT_NOT_INVENTORY') {
|
||||
@@ -306,4 +333,4 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export default class OrganizationController extends BaseController{
|
||||
errors: [{ type: 'TENANT.NOT.FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'tenant_seeded') {
|
||||
if (error.errorType === 'tenant_already_seeded') {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'TENANT.DATABASE.ALREADY.SEEDED', code: 200 }],
|
||||
});
|
||||
|
||||
@@ -49,8 +49,8 @@ export default () => {
|
||||
app.use('/invite', Container.get(InviteUsers).nonAuthRouter());
|
||||
app.use('/licenses', Container.get(Licenses).router());
|
||||
app.use('/subscription', Container.get(Subscription).router());
|
||||
|
||||
app.use('/organization', Container.get(Organization).router());
|
||||
app.use('/ping', Container.get(Ping).router());
|
||||
|
||||
// - Settings routes.
|
||||
// ---------------------------
|
||||
@@ -81,7 +81,6 @@ export default () => {
|
||||
dashboard.use(EnsureConfiguredMiddleware);
|
||||
dashboard.use(EnsureTenantIsSeeded);
|
||||
|
||||
dashboard.use('/ping', Container.get(Ping).router());
|
||||
|
||||
dashboard.use('/users', Container.get(Users).router());
|
||||
dashboard.use('/invite', Container.get(InviteUsers).authRouter());
|
||||
|
||||
@@ -13,7 +13,7 @@ const attachCurrentUser = async (req: Request, res: Response, next: Function) =>
|
||||
|
||||
try {
|
||||
Logger.info('[attach_user_middleware] finding system user by id.');
|
||||
const user = await systemUserRepository.getById(req.token.id);
|
||||
const user = await systemUserRepository.findOneById(req.token.id);
|
||||
console.log(user);
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -20,7 +20,7 @@ export default async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { tenantRepository } = Container.get('repositories');
|
||||
|
||||
Logger.info('[tenancy_middleware] trying get tenant by org. id from storage.');
|
||||
const tenant = await tenantRepository.getByOrgId(organizationId);
|
||||
const tenant = await tenantRepository.findOne({ organizationId });
|
||||
|
||||
// When the given organization id not found on the system storage.
|
||||
if (!tenant) {
|
||||
|
||||
@@ -160,5 +160,8 @@ export default {
|
||||
],
|
||||
blacklist: [],
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
protocol: '',
|
||||
hostname: '',
|
||||
};
|
||||
@@ -2,38 +2,43 @@ import { Container, Inject } from 'typedi';
|
||||
import InviteUserService from 'services/InviteUsers';
|
||||
|
||||
export default class UserInviteMailJob {
|
||||
@Inject()
|
||||
inviteUsersService: InviteUserService;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Agenda} agenda
|
||||
* @param {Agenda} agenda
|
||||
*/
|
||||
constructor(agenda) {
|
||||
agenda.define(
|
||||
'user-invite-mail',
|
||||
{ priority: 'high' },
|
||||
this.handler.bind(this),
|
||||
this.handler.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle invite user job.
|
||||
* @param {Job} job
|
||||
* @param {Function} done
|
||||
* @param {Job} job
|
||||
* @param {Function} done
|
||||
*/
|
||||
public async handler(job, done: Function): Promise<void> {
|
||||
const { email, organizationName, firstName } = job.attrs.data;
|
||||
const { invite, authorizedUser, tenantId } = job.attrs.data;
|
||||
|
||||
const Logger = Container.get('logger');
|
||||
const inviteUsersService = Container.get(InviteUserService);
|
||||
|
||||
Logger.info(`Send invite user mail - started: ${job.attrs.data}`);
|
||||
|
||||
|
||||
try {
|
||||
await this.inviteUsersService.mailMessages.sendInviteMail();
|
||||
await inviteUsersService.mailMessages.sendInviteMail(
|
||||
tenantId,
|
||||
authorizedUser,
|
||||
invite
|
||||
);
|
||||
Logger.info(`Send invite user mail - finished: ${job.attrs.data}`);
|
||||
done()
|
||||
done();
|
||||
} catch (error) {
|
||||
Logger.info(`Send invite user mail - error: ${job.attrs.data}, error: ${error}`);
|
||||
Logger.info(
|
||||
`Send invite user mail - error: ${job.attrs.data}, error: ${error}`
|
||||
);
|
||||
done(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Here we import all events.
|
||||
import 'subscribers/authentication';
|
||||
import 'subscribers/organization';
|
||||
import 'subscribers/inviteUser';
|
||||
import 'subscribers/manualJournals';
|
||||
import 'subscribers/expenses';
|
||||
import 'subscribers/bills';
|
||||
|
||||
@@ -21,10 +21,8 @@ export default async ({ expressApp }) => {
|
||||
objectionLoader({ knex });
|
||||
|
||||
// It returns the agenda instance because it's needed in the subsequent loaders
|
||||
const { agenda } = await dependencyInjectorLoader({
|
||||
mongoConnection,
|
||||
knex,
|
||||
});
|
||||
const { agenda } = await dependencyInjectorLoader({ mongoConnection, knex });
|
||||
|
||||
await jobsLoader({ agenda });
|
||||
Logger.info('[init] Jobs loaded');
|
||||
|
||||
|
||||
@@ -6,9 +6,12 @@ import {
|
||||
} from 'system/repositories';
|
||||
|
||||
export default () => {
|
||||
const knex = Container.get('knex');
|
||||
const cache = Container.get('cache');
|
||||
|
||||
return {
|
||||
systemUserRepository: Container.get(SystemUserRepository),
|
||||
subscriptionRepository: Container.get(SubscriptionRepository),
|
||||
tenantRepository: Container.get(TenantRepository),
|
||||
systemUserRepository: new SystemUserRepository(knex, cache),
|
||||
subscriptionRepository: new SubscriptionRepository(knex, cache),
|
||||
tenantRepository: new TenantRepository(knex, cache),
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Model, QueryBuilder } from 'objection';
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import PaginationQueryBuilder from './Pagination';
|
||||
|
||||
|
||||
class CustomerQueryBuilder extends QueryBuilder {
|
||||
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import { snakeCase, each } from 'lodash';
|
||||
import { snakeCase } from 'lodash';
|
||||
import { mapKeysDeep } from 'utils';
|
||||
import PaginationQueryBuilder from 'models/Pagination';
|
||||
import DateSession from 'models/DateSession';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Model, QueryBuilder } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import PaginationQueryBuilder from './Pagination';
|
||||
|
||||
|
||||
class VendorQueryBuilder extends QueryBuilder {
|
||||
class VendorQueryBuilder extends PaginationQueryBuilder {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { Account } from 'models';
|
||||
import TenantRepository from 'repositories/TenantRepository';
|
||||
import { IAccount } from 'interfaces';
|
||||
|
||||
export default class AccountRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Account;
|
||||
get model() {
|
||||
return Account.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts dependency graph.
|
||||
* @returns {}
|
||||
*/
|
||||
async getDependencyGraph(withRelation) {
|
||||
const accounts = await this.all(withRelation);
|
||||
async getDependencyGraph(withRelation) {
|
||||
const cacheKey = this.getCacheKey('accounts.depGraph', withRelation);
|
||||
|
||||
return this.cache.get(cacheKey, async () => {
|
||||
const accounts = await this.all(withRelation);
|
||||
|
||||
return this.model.toDependencyGraph(accounts);
|
||||
});
|
||||
}
|
||||
@@ -35,4 +36,50 @@ export default class AccountRepository extends TenantRepository {
|
||||
await this.model.query().where('id', accountId)[method]('amount', amount);
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
activateById(userId: number): Promise<IAccount> {
|
||||
return super.update({ active: 1 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
inactivateById(userId: number): Promise<void> {
|
||||
return super.update({ active: 0 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async activateByIds(userIds: number[]): Promise<IAccount> {
|
||||
const results = await this.model.query()
|
||||
.whereIn('id', userIds)
|
||||
.patch({ active: true });
|
||||
|
||||
this.flushCache();
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async inactivateByIds(userIds: number[]): Promise<IAccount> {
|
||||
const results = await this.model.query()
|
||||
.whereIn('id', userIds)
|
||||
.patch({ active: false });
|
||||
|
||||
this.flushCache();
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,10 @@ interface IJournalTransactionsFilter {
|
||||
|
||||
export default class AccountTransactionsRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = AccountTransaction;
|
||||
get model() {
|
||||
return AccountTransaction.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
journal(filter: IJournalTransactionsFilter) {
|
||||
|
||||
@@ -4,11 +4,10 @@ import { AccountType } from 'models';
|
||||
|
||||
export default class AccountTypeRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = AccountType;
|
||||
get model() {
|
||||
return AccountType.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,9 @@ import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
export default class BillRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Bill;
|
||||
get model() {
|
||||
return Bill.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export default class CachableRepository extends EntityRepository{
|
||||
constructor(knex, cache) {
|
||||
super(knex);
|
||||
this.cache = cache;
|
||||
this.repositoryName = this.constructor.name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,10 +4,9 @@ import { Contact } from 'models'
|
||||
|
||||
export default class ContactRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Contact;
|
||||
get model() {
|
||||
return Contact.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
import TenantRepository from "./TenantRepository";
|
||||
import { Customer } from 'models'
|
||||
import { Customer } from 'models';
|
||||
|
||||
export default class CustomerRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Contact repository.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Customer;
|
||||
this.repositoryName = 'ContactRepository';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
get model() {
|
||||
return Customer.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
changeBalance(vendorId: number, amount: number) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { cloneDeep, cloneDeepWith, forOwn, isString } from 'lodash';
|
||||
import ModelEntityNotFound from 'exceptions/ModelEntityNotFound';
|
||||
|
||||
export default class EntityRepository {
|
||||
modelInstance: any;
|
||||
idColumn: string;
|
||||
knex: any;
|
||||
|
||||
@@ -15,20 +14,11 @@ export default class EntityRepository {
|
||||
this.idColumn = 'id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model to the repository and bind it to knex instance.
|
||||
*/
|
||||
set model(model) {
|
||||
if (!this.modelInstance) {
|
||||
this.modelInstance = model.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the repository model binded it to knex instance.
|
||||
*/
|
||||
get model() {
|
||||
return this.modelInstance;
|
||||
throw new Error("The repository's model is not defined.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,9 @@ import { ExpenseCategory } from 'models';
|
||||
|
||||
export default class ExpenseEntyRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = ExpenseCategory;
|
||||
get model() {
|
||||
return ExpenseCategory.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,18 @@ import { Expense } from 'models';
|
||||
|
||||
export default class ExpenseRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Expense;
|
||||
get model() {
|
||||
return Expense.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish the given expense.
|
||||
* @param {number} expenseId
|
||||
*/
|
||||
async publish(expenseId: number): Promise<void> {
|
||||
super.update({
|
||||
publish(expenseId: number): Promise<void> {
|
||||
return super.update({
|
||||
id: expenseId,
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
|
||||
@@ -4,10 +4,9 @@ import TenantRepository from "./TenantRepository";
|
||||
|
||||
export default class ItemRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Item;
|
||||
get model() {
|
||||
return Item.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@ import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
export default class JournalRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = ManualJournal;
|
||||
get model() {
|
||||
return ManualJournal.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,10 @@ import { PaymentReceiveEntry } from 'models';
|
||||
import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
export default class PaymentReceiveEntryRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
/**
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = PaymentReceiveEntry;
|
||||
get model() {
|
||||
return PaymentReceiveEntry.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@ import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
export default class PaymentReceiveRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = PaymentReceive;
|
||||
get model() {
|
||||
return PaymentReceive.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
export default class SaleInvoiceRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = SaleInvoice;
|
||||
}
|
||||
get model() {
|
||||
return SaleInvoice.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@ import Setting from 'models/Setting';
|
||||
|
||||
export default class SettingRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Setting;
|
||||
get model() {
|
||||
return Setting.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,5 @@ export default class TenantRepository extends CachableRepository {
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.repositoryName = this.constructor.name;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,18 @@ import TenantRepository from "./TenantRepository";
|
||||
|
||||
export default class VendorRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Contact repository.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = Vendor;
|
||||
this.repositoryName = 'ContactRepository';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
get model() {
|
||||
return Vendor.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
changeBalance(vendorId: number, amount: number) {
|
||||
|
||||
@@ -3,11 +3,10 @@ import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
export default class ViewRepository extends TenantRepository {
|
||||
/**
|
||||
* Constructor method.
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
constructor(knex, cache) {
|
||||
super(knex, cache);
|
||||
this.model = View;
|
||||
get model() {
|
||||
return View.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { difference, chain, uniq } from 'lodash';
|
||||
import { kebabCase } from 'lodash'
|
||||
import { kebabCase } from 'lodash';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import { IAccountDTO, IAccount, IAccountsFilter, IFilterMeta } from 'interfaces';
|
||||
import {
|
||||
IAccountDTO,
|
||||
IAccount,
|
||||
IAccountsFilter,
|
||||
IFilterMeta,
|
||||
} from 'interfaces';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -27,15 +32,21 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Retrieve account type or throws service error.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} accountTypeId -
|
||||
* @return {IAccountType}
|
||||
* @param {number} tenantId -
|
||||
* @param {number} accountTypeId -
|
||||
* @return {IAccountType}
|
||||
*/
|
||||
private async getAccountTypeOrThrowError(tenantId: number, accountTypeId: number) {
|
||||
const { AccountType } = this.tenancy.models(tenantId);
|
||||
private async getAccountTypeOrThrowError(
|
||||
tenantId: number,
|
||||
accountTypeId: number
|
||||
) {
|
||||
const { accountTypeRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[accounts] validating account type existance.', { tenantId, accountTypeId });
|
||||
const accountType = await AccountType.query().findById(accountTypeId);
|
||||
this.logger.info('[accounts] validating account type existance.', {
|
||||
tenantId,
|
||||
accountTypeId,
|
||||
});
|
||||
const accountType = await accountTypeRepository.findOneById(accountTypeId);
|
||||
|
||||
if (!accountType) {
|
||||
this.logger.info('[accounts] account type not found.');
|
||||
@@ -46,24 +57,34 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Retrieve parent account or throw service error.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} notAccountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} notAccountId
|
||||
*/
|
||||
private async getParentAccountOrThrowError(tenantId: number, accountId: number, notAccountId?: number) {
|
||||
private async getParentAccountOrThrowError(
|
||||
tenantId: number,
|
||||
accountId: number,
|
||||
notAccountId?: number
|
||||
) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[accounts] validating parent account existance.', {
|
||||
tenantId, accountId, notAccountId,
|
||||
tenantId,
|
||||
accountId,
|
||||
notAccountId,
|
||||
});
|
||||
const parentAccount = await Account.query().findById(accountId)
|
||||
const parentAccount = await Account.query()
|
||||
.findById(accountId)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (!parentAccount) {
|
||||
this.logger.info('[accounts] parent account not found.', { tenantId, accountId });
|
||||
this.logger.info('[accounts] parent account not found.', {
|
||||
tenantId,
|
||||
accountId,
|
||||
});
|
||||
throw new ServiceError('parent_account_not_found');
|
||||
}
|
||||
return parentAccount;
|
||||
@@ -71,17 +92,27 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Throws error if the account type was not unique on the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {string} accountCode
|
||||
* @param {number} notAccountId
|
||||
* @param {number} tenantId
|
||||
* @param {string} accountCode
|
||||
* @param {number} notAccountId
|
||||
*/
|
||||
private async isAccountCodeUniqueOrThrowError(tenantId: number, accountCode: string, notAccountId?: number) {
|
||||
private async isAccountCodeUniqueOrThrowError(
|
||||
tenantId: number,
|
||||
accountCode: string,
|
||||
notAccountId?: number
|
||||
) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[accounts] validating the account code unique on the storage.', {
|
||||
tenantId, accountCode, notAccountId,
|
||||
});
|
||||
const account = await Account.query().where('code', accountCode)
|
||||
this.logger.info(
|
||||
'[accounts] validating the account code unique on the storage.',
|
||||
{
|
||||
tenantId,
|
||||
accountCode,
|
||||
notAccountId,
|
||||
}
|
||||
);
|
||||
const account = await Account.query()
|
||||
.where('code', accountCode)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
@@ -89,17 +120,23 @@ export default class AccountsService {
|
||||
});
|
||||
|
||||
if (account.length > 0) {
|
||||
this.logger.info('[accounts] account code is not unique.', { tenantId, accountCode });
|
||||
this.logger.info('[accounts] account code is not unique.', {
|
||||
tenantId,
|
||||
accountCode,
|
||||
});
|
||||
throw new ServiceError('account_code_not_unique');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws service error if parent account has different type.
|
||||
* @param {IAccountDTO} accountDTO
|
||||
* @param {IAccount} parentAccount
|
||||
* @param {IAccountDTO} accountDTO
|
||||
* @param {IAccount} parentAccount
|
||||
*/
|
||||
private throwErrorIfParentHasDiffType(accountDTO: IAccountDTO, parentAccount: IAccount) {
|
||||
private throwErrorIfParentHasDiffType(
|
||||
accountDTO: IAccountDTO,
|
||||
parentAccount: IAccount
|
||||
) {
|
||||
if (accountDTO.accountTypeId !== parentAccount.accountTypeId) {
|
||||
throw new ServiceError('parent_has_different_type');
|
||||
}
|
||||
@@ -107,18 +144,23 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Retrieve account of throw service error in case account not found.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @return {IAccount}
|
||||
*/
|
||||
private async getAccountOrThrowError(tenantId: number, accountId: number) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[accounts] validating the account existance.', { tenantId, accountId });
|
||||
this.logger.info('[accounts] validating the account existance.', {
|
||||
tenantId,
|
||||
accountId,
|
||||
});
|
||||
const account = await accountRepository.findOneById(accountId);
|
||||
|
||||
if (!account) {
|
||||
this.logger.info('[accounts] the given account not found.', { accountId });
|
||||
this.logger.info('[accounts] the given account not found.', {
|
||||
accountId,
|
||||
});
|
||||
throw new ServiceError('account_not_found');
|
||||
}
|
||||
return account;
|
||||
@@ -127,13 +169,13 @@ export default class AccountsService {
|
||||
/**
|
||||
* Diff account type between new and old account, throw service error
|
||||
* if they have different account type.
|
||||
*
|
||||
*
|
||||
* @param {IAccount|IAccountDTO} oldAccount
|
||||
* @param {IAccount|IAccountDTO} newAccount
|
||||
*/
|
||||
private async isAccountTypeChangedOrThrowError(
|
||||
oldAccount: IAccount|IAccountDTO,
|
||||
newAccount: IAccount|IAccountDTO,
|
||||
oldAccount: IAccount | IAccountDTO,
|
||||
newAccount: IAccount | IAccountDTO
|
||||
) {
|
||||
if (oldAccount.accountTypeId !== newAccount.accountTypeId) {
|
||||
throw new ServiceError('account_type_not_allowed_to_changed');
|
||||
@@ -142,19 +184,29 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Validates the account name uniquiness.
|
||||
* @param {number} tenantId
|
||||
* @param {string} accountName
|
||||
* @param {number} tenantId
|
||||
* @param {string} accountName
|
||||
* @param {number} notAccountId - Ignore the account id.
|
||||
*/
|
||||
private async validateAccountNameUniquiness(tenantId: number, accountName: string, notAccountId?: number) {
|
||||
private async validateAccountNameUniquiness(
|
||||
tenantId: number,
|
||||
accountName: string,
|
||||
notAccountId?: number
|
||||
) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[accounts] validating account name uniquiness.', { tenantId, accountName, notAccountId });
|
||||
const foundAccount = await Account.query().findOne('name', accountName).onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
this.logger.info('[accounts] validating account name uniquiness.', {
|
||||
tenantId,
|
||||
accountName,
|
||||
notAccountId,
|
||||
});
|
||||
const foundAccount = await Account.query()
|
||||
.findOne('name', accountName)
|
||||
.onBuild((query) => {
|
||||
if (notAccountId) {
|
||||
query.whereNot('id', notAccountId);
|
||||
}
|
||||
});
|
||||
if (foundAccount) {
|
||||
throw new ServiceError('account_name_not_unqiue');
|
||||
}
|
||||
@@ -162,9 +214,9 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Creates a new account on the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {IAccount} accountDTO
|
||||
* @returns {IAccount}
|
||||
* @param {number} tenantId
|
||||
* @param {IAccount} accountDTO
|
||||
* @returns {IAccount}
|
||||
*/
|
||||
public async newAccount(tenantId: number, accountDTO: IAccountDTO) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
@@ -180,7 +232,8 @@ export default class AccountsService {
|
||||
|
||||
if (accountDTO.parentAccountId) {
|
||||
const parentAccount = await this.getParentAccountOrThrowError(
|
||||
tenantId, accountDTO.parentAccountId
|
||||
tenantId,
|
||||
accountDTO.parentAccountId
|
||||
);
|
||||
this.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
|
||||
@@ -191,7 +244,10 @@ export default class AccountsService {
|
||||
...accountDTO,
|
||||
slug: kebabCase(accountDTO.name),
|
||||
});
|
||||
this.logger.info('[account] account created successfully.', { account, accountDTO });
|
||||
this.logger.info('[account] account created successfully.', {
|
||||
account,
|
||||
accountDTO,
|
||||
});
|
||||
|
||||
// Triggers `onAccountCreated` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onCreated);
|
||||
@@ -201,21 +257,31 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Edits details of the given account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {IAccountDTO} accountDTO
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {IAccountDTO} accountDTO
|
||||
*/
|
||||
public async editAccount(tenantId: number, accountId: number, accountDTO: IAccountDTO) {
|
||||
this.logger.info('[account] trying to edit account.', { tenantId, accountId });
|
||||
|
||||
public async editAccount(
|
||||
tenantId: number,
|
||||
accountId: number,
|
||||
accountDTO: IAccountDTO
|
||||
) {
|
||||
this.logger.info('[account] trying to edit account.', {
|
||||
tenantId,
|
||||
accountId,
|
||||
});
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const oldAccount = await this.getAccountOrThrowError(tenantId, accountId);
|
||||
|
||||
// Validate account name uniquiness.
|
||||
await this.validateAccountNameUniquiness(tenantId, accountDTO.name, accountId);
|
||||
await this.validateAccountNameUniquiness(
|
||||
tenantId,
|
||||
accountDTO.name,
|
||||
accountId
|
||||
);
|
||||
|
||||
await this.isAccountTypeChangedOrThrowError(oldAccount, accountDTO);
|
||||
|
||||
|
||||
// Validate the account code not exists on the storage.
|
||||
if (accountDTO.code && accountDTO.code !== oldAccount.code) {
|
||||
await this.isAccountCodeUniqueOrThrowError(
|
||||
@@ -226,17 +292,21 @@ export default class AccountsService {
|
||||
}
|
||||
if (accountDTO.parentAccountId) {
|
||||
const parentAccount = await this.getParentAccountOrThrowError(
|
||||
tenantId, accountDTO.parentAccountId, oldAccount.id,
|
||||
tenantId,
|
||||
accountDTO.parentAccountId,
|
||||
oldAccount.id
|
||||
);
|
||||
this.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
}
|
||||
// Update the account on the storage.
|
||||
const account = await accountRepository.updateAndFetch({
|
||||
id: oldAccount.id,
|
||||
...accountDTO
|
||||
});
|
||||
const account = await accountRepository.update(
|
||||
{ ...accountDTO, },
|
||||
{ id: oldAccount.id }
|
||||
);
|
||||
this.logger.info('[account] account edited successfully.', {
|
||||
account, accountDTO, tenantId
|
||||
account,
|
||||
accountDTO,
|
||||
tenantId,
|
||||
});
|
||||
// Triggers `onAccountEdited` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onEdited);
|
||||
@@ -246,8 +316,8 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Retrieve the given account details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public async getAccount(tenantId: number, accountId: number) {
|
||||
return this.getAccountOrThrowError(tenantId, accountId);
|
||||
@@ -255,22 +325,24 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Detarmine if the given account id exists on the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public async isAccountExists(tenantId: number, accountId: number) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[account] validating the account existance.', { tenantId, accountId });
|
||||
const foundAccounts = await Account.query()
|
||||
.where('id', accountId);
|
||||
this.logger.info('[account] validating the account existance.', {
|
||||
tenantId,
|
||||
accountId,
|
||||
});
|
||||
const foundAccounts = await Account.query().where('id', accountId);
|
||||
|
||||
return foundAccounts.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws error if the account was prefined.
|
||||
* @param {IAccount} account
|
||||
* @param {IAccount} account
|
||||
*/
|
||||
private throwErrorIfAccountPredefined(account: IAccount) {
|
||||
if (account.predefined) {
|
||||
@@ -281,14 +353,16 @@ export default class AccountsService {
|
||||
/**
|
||||
* Unlink the given parent account with children accounts.
|
||||
* @param {number} tenantId -
|
||||
* @param {number|number[]} parentAccountId -
|
||||
* @param {number|number[]} parentAccountId -
|
||||
*/
|
||||
private async unassociateChildrenAccountsFromParent(
|
||||
tenantId: number,
|
||||
parentAccountId: number | number[],
|
||||
parentAccountId: number | number[]
|
||||
) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const accountsIds = Array.isArray(parentAccountId) ? parentAccountId : [parentAccountId];
|
||||
const accountsIds = Array.isArray(parentAccountId)
|
||||
? parentAccountId
|
||||
: [parentAccountId];
|
||||
|
||||
await Account.query()
|
||||
.whereIn('parent_account_id', accountsIds)
|
||||
@@ -297,13 +371,17 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Throws service error if the account has associated transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
*/
|
||||
private async throwErrorIfAccountHasTransactions(tenantId: number, accountId: number) {
|
||||
private async throwErrorIfAccountHasTransactions(
|
||||
tenantId: number,
|
||||
accountId: number
|
||||
) {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
const accountTransactions = await AccountTransaction.query().where(
|
||||
'account_id', accountId,
|
||||
'account_id',
|
||||
accountId
|
||||
);
|
||||
if (accountTransactions.length > 0) {
|
||||
throw new ServiceError('account_has_associated_transactions');
|
||||
@@ -312,8 +390,8 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Deletes the account from the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
*/
|
||||
public async deleteAccount(tenantId: number, accountId: number) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
@@ -330,7 +408,8 @@ export default class AccountsService {
|
||||
|
||||
await accountRepository.deleteById(account.id);
|
||||
this.logger.info('[account] account has been deleted successfully.', {
|
||||
tenantId, accountId,
|
||||
tenantId,
|
||||
accountId,
|
||||
});
|
||||
|
||||
// Triggers `onAccountDeleted` event.
|
||||
@@ -339,8 +418,8 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Retrieve the given accounts details or throw error if one account not exists.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
* @return {IAccount[]}
|
||||
*/
|
||||
public async getAccountsOrThrowError(
|
||||
@@ -349,13 +428,19 @@ export default class AccountsService {
|
||||
): Promise<IAccount[]> {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[account] trying to validate accounts not exist.', { tenantId, accountsIds });
|
||||
this.logger.info('[account] trying to validate accounts not exist.', {
|
||||
tenantId,
|
||||
accountsIds,
|
||||
});
|
||||
const storedAccounts = await Account.query().whereIn('id', accountsIds);
|
||||
const storedAccountsIds = storedAccounts.map((account) => account.id);
|
||||
const notFoundAccounts = difference(accountsIds, storedAccountsIds);
|
||||
|
||||
if (notFoundAccounts.length > 0) {
|
||||
this.logger.error('[account] accounts not exists on the storage.', { tenantId, notFoundAccounts });
|
||||
this.logger.error('[account] accounts not exists on the storage.', {
|
||||
tenantId,
|
||||
notFoundAccounts,
|
||||
});
|
||||
throw new ServiceError('accounts_not_found');
|
||||
}
|
||||
return storedAccounts;
|
||||
@@ -367,7 +452,9 @@ export default class AccountsService {
|
||||
* @return {IAccount[]} - Predefined accounts
|
||||
*/
|
||||
private validatePrefinedAccounts(accounts: IAccount[]) {
|
||||
const predefined = accounts.filter((account: IAccount) => account.predefined);
|
||||
const predefined = accounts.filter(
|
||||
(account: IAccount) => account.predefined
|
||||
);
|
||||
|
||||
if (predefined.length > 0) {
|
||||
this.logger.error('[accounts] some accounts predefined.', { predefined });
|
||||
@@ -378,10 +465,13 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Validating the accounts have associated transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
*/
|
||||
private async validateAccountsHaveTransactions(tenantId: number, accountsIds: number[]) {
|
||||
private async validateAccountsHaveTransactions(
|
||||
tenantId: number,
|
||||
accountsIds: number[]
|
||||
) {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
const accountsTransactions = await AccountTransaction.query()
|
||||
.whereIn('account_id', accountsIds)
|
||||
@@ -403,11 +493,11 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Deletes the given accounts in bulk.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
*/
|
||||
public async deleteAccounts(tenantId: number, accountsIds: number[]) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const { accountRepository } = this.tenancy.models(tenantId);
|
||||
const accounts = await this.getAccountsOrThrowError(tenantId, accountsIds);
|
||||
|
||||
// Validate the accounts are not predefined.
|
||||
@@ -420,10 +510,11 @@ export default class AccountsService {
|
||||
await this.unassociateChildrenAccountsFromParent(tenantId, accountsIds);
|
||||
|
||||
// Delete the accounts in one query.
|
||||
await Account.query().whereIn('id', accountsIds).delete();
|
||||
await accountRepository.deleteWhereIdIn(accountsIds);
|
||||
|
||||
this.logger.info('[account] given accounts deleted in bulk successfully.', {
|
||||
tenantId, accountsIds
|
||||
tenantId,
|
||||
accountsIds,
|
||||
});
|
||||
// Triggers `onBulkDeleted` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onBulkDeleted);
|
||||
@@ -431,12 +522,15 @@ export default class AccountsService {
|
||||
|
||||
/**
|
||||
* Activate accounts in bulk.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
* @param {boolean} activate
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} accountsIds
|
||||
* @param {boolean} activate
|
||||
*/
|
||||
public async activateAccounts(tenantId: number, accountsIds: number[], activate: boolean = true) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
public async activateAccounts(
|
||||
tenantId: number,
|
||||
accountsIds: number[],
|
||||
activate: boolean = true
|
||||
) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the given account or throw not found.
|
||||
@@ -445,32 +539,41 @@ export default class AccountsService {
|
||||
// Get all children accounts.
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
const dependenciesAccounts = chain(accountsIds)
|
||||
.map(accountId => accountsGraph.dependenciesOf(accountId))
|
||||
.map((accountId) => accountsGraph.dependenciesOf(accountId))
|
||||
.flatten()
|
||||
.value();
|
||||
|
||||
// The children and parent accounts.
|
||||
const patchAccountsIds = uniq([...dependenciesAccounts, accountsIds]);
|
||||
|
||||
this.logger.info('[account] trying activate/inactive the given accounts ids.', { accountsIds });
|
||||
await Account.query().whereIn('id', patchAccountsIds)
|
||||
.patch({
|
||||
active: activate ? 1 : 0,
|
||||
});
|
||||
this.logger.info('[account] accounts have been activated successfully.', { tenantId, accountsIds });
|
||||
this.logger.info(
|
||||
'[account] trying activate/inactive the given accounts ids.',
|
||||
{ accountsIds }
|
||||
);
|
||||
// Activate or inactivate the given accounts ids in bulk.
|
||||
(activate) ?
|
||||
await accountRepository.activateByIds(patchAccountsIds) :
|
||||
await accountRepository.inactivateByIds(patchAccountsIds);
|
||||
|
||||
this.logger.info('[account] accounts have been activated successfully.', {
|
||||
tenantId,
|
||||
accountsIds,
|
||||
});
|
||||
// Triggers `onAccountBulkActivated` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates/Inactivates the given account.
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {boolean} activate
|
||||
* @param {number} tenantId
|
||||
* @param {number} accountId
|
||||
* @param {boolean} activate
|
||||
*/
|
||||
public async activateAccount(tenantId: number, accountId: number, activate?: boolean) {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
public async activateAccount(
|
||||
tenantId: number,
|
||||
accountId: number,
|
||||
activate?: boolean
|
||||
) {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the given account or throw not found error.
|
||||
@@ -480,34 +583,44 @@ export default class AccountsService {
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
const dependenciesAccounts = accountsGraph.dependenciesOf(accountId);
|
||||
|
||||
this.logger.info('[account] trying to activate/inactivate the given account id.');
|
||||
await Account.query()
|
||||
.whereIn('id', [...dependenciesAccounts, accountId])
|
||||
.patch({
|
||||
active: activate ? 1 : 0,
|
||||
})
|
||||
this.logger.info(
|
||||
'[account] trying to activate/inactivate the given account id.'
|
||||
);
|
||||
const patchAccountsIds = [...dependenciesAccounts, accountId];
|
||||
|
||||
// Activate and inactivate the given accounts ids.
|
||||
(activate) ?
|
||||
await accountRepository.activateByIds(patchAccountsIds) :
|
||||
await accountRepository.inactivateByIds(patchAccountsIds);
|
||||
|
||||
this.logger.info('[account] account have been activated successfully.', {
|
||||
tenantId,
|
||||
accountId
|
||||
accountId,
|
||||
});
|
||||
|
||||
// Triggers `onAccountActivated` event.
|
||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Retrieve accounts datatable list.
|
||||
* @param {number} tenantId
|
||||
* @param {IAccountsFilter} accountsFilter
|
||||
* @param {number} tenantId
|
||||
* @param {IAccountsFilter} accountsFilter
|
||||
*/
|
||||
public async getAccountsList(
|
||||
tenantId: number,
|
||||
filter: IAccountsFilter,
|
||||
): Promise<{ accounts: IAccount[], filterMeta: IFilterMeta }> {
|
||||
filter: IAccountsFilter
|
||||
): Promise<{ accounts: IAccount[]; filterMeta: IFilterMeta }> {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const dynamicList = await this.dynamicListService.dynamicList(tenantId, Account, filter);
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Account,
|
||||
filter
|
||||
);
|
||||
|
||||
this.logger.info('[accounts] trying to get accounts datatable list.', { tenantId, filter });
|
||||
this.logger.info('[accounts] trying to get accounts datatable list.', {
|
||||
tenantId,
|
||||
filter,
|
||||
});
|
||||
const accounts = await Account.query().onBuild((builder) => {
|
||||
builder.withGraphFetched('type');
|
||||
dynamicList.buildQuery()(builder);
|
||||
@@ -527,7 +640,7 @@ export default class AccountsService {
|
||||
* - Transfer the given account transactions to another account with the same root type.
|
||||
* - Delete the given account.
|
||||
* -------
|
||||
* @param {number} tenantId -
|
||||
* @param {number} tenantId -
|
||||
* @param {number} accountId -
|
||||
* @param {number} toAccountId -
|
||||
* @param {boolean} deleteAfterClosing -
|
||||
@@ -536,32 +649,47 @@ export default class AccountsService {
|
||||
tenantId: number,
|
||||
accountId: number,
|
||||
toAccountId: number,
|
||||
deleteAfterClosing: boolean,
|
||||
deleteAfterClosing: boolean
|
||||
) {
|
||||
this.logger.info('[account] trying to close account.', { tenantId, accountId, toAccountId, deleteAfterClosing });
|
||||
this.logger.info('[account] trying to close account.', {
|
||||
tenantId,
|
||||
accountId,
|
||||
toAccountId,
|
||||
deleteAfterClosing,
|
||||
});
|
||||
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const {
|
||||
accountTypeRepository,
|
||||
accountRepository,
|
||||
} = this.tenancy.repositories(tenantId);
|
||||
|
||||
const account = await this.getAccountOrThrowError(tenantId, accountId);
|
||||
const toAccount = await this.getAccountOrThrowError(tenantId, toAccountId);
|
||||
|
||||
this.throwErrorIfAccountPredefined(account);
|
||||
|
||||
const accountType = await accountTypeRepository.findOneById(account.accountTypeId);
|
||||
const toAccountType = await accountTypeRepository.findOneById(toAccount.accountTypeId);
|
||||
const accountType = await accountTypeRepository.findOneById(
|
||||
account.accountTypeId
|
||||
);
|
||||
const toAccountType = await accountTypeRepository.findOneById(
|
||||
toAccount.accountTypeId
|
||||
);
|
||||
|
||||
if (accountType.rootType !== toAccountType.rootType) {
|
||||
throw new ServiceError('close_account_and_to_account_not_same_type');
|
||||
}
|
||||
const updateAccountBalanceOper = await accountRepository.balanceChange(accountId, account.balance || 0);
|
||||
const updateAccountBalanceOper = await accountRepository.balanceChange(
|
||||
accountId,
|
||||
account.balance || 0
|
||||
);
|
||||
|
||||
// Move transactiosn operation.
|
||||
const moveTransactionsOper = await AccountTransaction.query()
|
||||
.where('account_id', accountId)
|
||||
.patch({ accountId: toAccountId });
|
||||
|
||||
await Promise.all([ moveTransactionsOper, updateAccountBalanceOper ]);
|
||||
await Promise.all([moveTransactionsOper, updateAccountBalanceOper]);
|
||||
|
||||
if (deleteAfterClosing) {
|
||||
await accountRepository.deleteById(accountId);
|
||||
|
||||
@@ -4,17 +4,17 @@ import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { IAccountsTypesService, IAccountType } from 'interfaces';
|
||||
|
||||
@Service()
|
||||
export default class AccountsTypesService implements IAccountsTypesService{
|
||||
export default class AccountsTypesService implements IAccountsTypesService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve all accounts types.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} tenantId -
|
||||
* @return {Promise<IAccountType>}
|
||||
*/
|
||||
async getAccountsTypes(tenantId: number): Promise<IAccountType> {
|
||||
const { accountTypeRepository } = this.tenancy.repositories(tenantId);
|
||||
return accountTypeRepository.all();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Service, Inject, Container } from 'typedi';
|
||||
import JWT from 'jsonwebtoken';
|
||||
import uniqid from 'uniqid';
|
||||
import { omit } from 'lodash';
|
||||
import { omit, cloneDeep } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
EventDispatcher,
|
||||
@@ -62,7 +62,6 @@ export default class AuthenticationService implements IAuthenticationService {
|
||||
emailOrPhone,
|
||||
password,
|
||||
});
|
||||
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
const loginThrottler = Container.get('rateLimiter.login');
|
||||
|
||||
@@ -108,10 +107,13 @@ export default class AuthenticationService implements IAuthenticationService {
|
||||
});
|
||||
const tenant = await user.$relatedQuery('tenant');
|
||||
|
||||
// Remove password property from user object.
|
||||
Reflect.deleteProperty(user, 'password');
|
||||
// Keep the user object immutable
|
||||
const outputUser = cloneDeep(user);
|
||||
|
||||
return { user, token, tenant };
|
||||
// Remove password property from user object.
|
||||
Reflect.deleteProperty(outputUser, 'password');
|
||||
|
||||
return { user: outputUser, token, tenant };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,10 +123,10 @@ export default class AuthenticationService implements IAuthenticationService {
|
||||
*/
|
||||
private async validateEmailAndPhoneUniqiness(registerDTO: IRegisterDTO) {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
const isEmailExists = await systemUserRepository.getByEmail(
|
||||
const isEmailExists = await systemUserRepository.findOneByEmail(
|
||||
registerDTO.email
|
||||
);
|
||||
const isPhoneExists = await systemUserRepository.getByPhoneNumber(
|
||||
const isPhoneExists = await systemUserRepository.findOneByPhoneNumber(
|
||||
registerDTO.phoneNumber
|
||||
);
|
||||
|
||||
@@ -190,7 +192,7 @@ export default class AuthenticationService implements IAuthenticationService {
|
||||
*/
|
||||
private async validateEmailExistance(email: string): Promise<ISystemUser> {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
const userByEmail = await systemUserRepository.getByEmail(email);
|
||||
const userByEmail = await systemUserRepository.findOneByEmail(email);
|
||||
|
||||
if (!userByEmail) {
|
||||
this.logger.info('[send_reset_password] The given email not found.');
|
||||
@@ -255,7 +257,7 @@ export default class AuthenticationService implements IAuthenticationService {
|
||||
await this.deletePasswordResetToken(tokenModel.email);
|
||||
throw new ServiceError('token_expired');
|
||||
}
|
||||
const user = await systemUserRepository.getByEmail(tokenModel.email);
|
||||
const user = await systemUserRepository.findOneByEmail(tokenModel.email);
|
||||
|
||||
if (!user) {
|
||||
throw new ServiceError('user_not_found');
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { difference, upperFirst, omit } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { ServiceError } from "exceptions";
|
||||
import { ServiceError } from 'exceptions';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import {
|
||||
IContact,
|
||||
IContactNewDTO,
|
||||
IContactEditDTO,
|
||||
} from "interfaces";
|
||||
import { IContact, IContactNewDTO, IContactEditDTO } from 'interfaces';
|
||||
import JournalPoster from '../Accounting/JournalPoster';
|
||||
|
||||
type TContactService = 'customer' | 'vendor';
|
||||
@@ -26,8 +22,8 @@ export default class ContactsService {
|
||||
|
||||
/**
|
||||
* Get the given contact or throw not found contact.
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {TContactService} contactService
|
||||
* @return {Promise<IContact>}
|
||||
*/
|
||||
@@ -38,10 +34,13 @@ export default class ContactsService {
|
||||
) {
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[contact] trying to validate contact existance.', { tenantId, contactId });
|
||||
this.logger.info('[contact] trying to validate contact existance.', {
|
||||
tenantId,
|
||||
contactId,
|
||||
});
|
||||
const contact = await contactRepository.findOne({
|
||||
id: contactId,
|
||||
...(contactService) && ({ contactService }),
|
||||
...(contactService && { contactService }),
|
||||
});
|
||||
|
||||
if (!contact) {
|
||||
@@ -52,13 +51,15 @@ export default class ContactsService {
|
||||
|
||||
/**
|
||||
* Converts contact DTO object to model object attributes to insert or update.
|
||||
* @param {IContactNewDTO | IContactEditDTO} contactDTO
|
||||
* @param {IContactNewDTO | IContactEditDTO} contactDTO
|
||||
*/
|
||||
private transformContactObj(contactDTO: IContactNewDTO | IContactEditDTO) {
|
||||
return {
|
||||
...omit(contactDTO, [
|
||||
'billingAddress1', 'billingAddress2',
|
||||
'shippingAddress1', 'shippingAddress2',
|
||||
'billingAddress1',
|
||||
'billingAddress2',
|
||||
'shippingAddress1',
|
||||
'shippingAddress2',
|
||||
]),
|
||||
billing_address_1: contactDTO?.billingAddress1,
|
||||
billing_address_2: contactDTO?.billingAddress2,
|
||||
@@ -69,54 +70,87 @@ export default class ContactsService {
|
||||
|
||||
/**
|
||||
* Creates a new contact on the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number} tenantId
|
||||
* @param {TContactService} contactService
|
||||
* @param {IContactDTO} contactDTO
|
||||
* @param {IContactDTO} contactDTO
|
||||
*/
|
||||
async newContact(
|
||||
tenantId: number,
|
||||
contactDTO: IContactNewDTO,
|
||||
contactService: TContactService,
|
||||
contactService: TContactService
|
||||
) {
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
const contactObj = this.transformContactObj(contactDTO);
|
||||
|
||||
this.logger.info('[contacts] trying to insert contact to the storage.', { tenantId, contactDTO });
|
||||
const contact = await contactRepository.create({ contactService, ...contactObj });
|
||||
this.logger.info('[contacts] trying to insert contact to the storage.', {
|
||||
tenantId,
|
||||
contactDTO,
|
||||
});
|
||||
const contact = await contactRepository.create({
|
||||
contactService,
|
||||
...contactObj,
|
||||
});
|
||||
|
||||
this.logger.info('[contacts] contact inserted successfully.', { tenantId, contact });
|
||||
this.logger.info('[contacts] contact inserted successfully.', {
|
||||
tenantId,
|
||||
contact,
|
||||
});
|
||||
return contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit details of the given on the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {TContactService} contactService
|
||||
* @param {IContactDTO} contactDTO
|
||||
* @param {IContactDTO} contactDTO
|
||||
*/
|
||||
async editContact(tenantId: number, contactId: number, contactDTO: IContactEditDTO, contactService: TContactService) {
|
||||
async editContact(
|
||||
tenantId: number,
|
||||
contactId: number,
|
||||
contactDTO: IContactEditDTO,
|
||||
contactService: TContactService
|
||||
) {
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
const contactObj = this.transformContactObj(contactDTO);
|
||||
|
||||
const contact = await this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||
const contact = await this.getContactByIdOrThrowError(
|
||||
tenantId,
|
||||
contactId,
|
||||
contactService
|
||||
);
|
||||
|
||||
this.logger.info('[contacts] trying to edit the given contact details.', { tenantId, contactId, contactDTO });
|
||||
this.logger.info('[contacts] trying to edit the given contact details.', {
|
||||
tenantId,
|
||||
contactId,
|
||||
contactDTO,
|
||||
});
|
||||
await contactRepository.update({ ...contactObj }, { id: contactId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given contact from the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {TContactService} contactService
|
||||
* @param {TContactService} contactService
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteContact(tenantId: number, contactId: number, contactService: TContactService) {
|
||||
async deleteContact(
|
||||
tenantId: number,
|
||||
contactId: number,
|
||||
contactService: TContactService
|
||||
) {
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
const contact = await this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||
const contact = await this.getContactByIdOrThrowError(
|
||||
tenantId,
|
||||
contactId,
|
||||
contactService
|
||||
);
|
||||
|
||||
this.logger.info('[contacts] trying to delete the given contact.', { tenantId, contactId });
|
||||
this.logger.info('[contacts] trying to delete the given contact.', {
|
||||
tenantId,
|
||||
contactId,
|
||||
});
|
||||
|
||||
// Deletes contact of the given id.
|
||||
await contactRepository.deleteById(contactId);
|
||||
@@ -124,26 +158,36 @@ export default class ContactsService {
|
||||
|
||||
/**
|
||||
* Get contact details of the given contact id.
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {TContactService} contactService
|
||||
* @returns {Promise<IContact>}
|
||||
*/
|
||||
async getContact(tenantId: number, contactId: number, contactService: TContactService) {
|
||||
async getContact(
|
||||
tenantId: number,
|
||||
contactId: number,
|
||||
contactService: TContactService
|
||||
) {
|
||||
return this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve contacts or throw not found error if one of ids were not found
|
||||
* on the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} contactsIds
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} contactsIds
|
||||
* @param {TContactService} contactService
|
||||
* @return {Promise<IContact>}
|
||||
*/
|
||||
async getContactsOrThrowErrorNotFound(tenantId: number, contactsIds: number[], contactService: TContactService) {
|
||||
async getContactsOrThrowErrorNotFound(
|
||||
tenantId: number,
|
||||
contactsIds: number[],
|
||||
contactService: TContactService
|
||||
) {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const contacts = await Contact.query().whereIn('id', contactsIds).where('contact_service', contactService);
|
||||
const contacts = await Contact.query()
|
||||
.whereIn('id', contactsIds)
|
||||
.where('contact_service', contactService);
|
||||
const storedContactsIds = contacts.map((contact: IContact) => contact.id);
|
||||
|
||||
const notFoundCustomers = difference(contactsIds, storedContactsIds);
|
||||
@@ -156,12 +200,16 @@ export default class ContactsService {
|
||||
|
||||
/**
|
||||
* Deletes the given contacts in bulk.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} contactsIds
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} contactsIds
|
||||
* @param {TContactService} contactService
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteBulkContacts(tenantId: number, contactsIds: number[], contactService: TContactService) {
|
||||
async deleteBulkContacts(
|
||||
tenantId: number,
|
||||
contactsIds: number[],
|
||||
contactService: TContactService
|
||||
) {
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
this.getContactsOrThrowErrorNotFound(tenantId, contactsIds, contactService);
|
||||
|
||||
@@ -170,9 +218,9 @@ export default class ContactsService {
|
||||
|
||||
/**
|
||||
* Reverts journal entries of the given contacts.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} contactsIds
|
||||
* @param {TContactService} contactService
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} contactsIds
|
||||
* @param {TContactService} contactService
|
||||
*/
|
||||
async revertJEntriesContactsOpeningBalance(
|
||||
tenantId: number,
|
||||
@@ -189,17 +237,14 @@ export default class ContactsService {
|
||||
journal.loadEntries(contactsTransactions);
|
||||
journal.removeEntries();
|
||||
|
||||
await Promise.all([
|
||||
journal.saveBalance(),
|
||||
journal.deleteEntries(),
|
||||
]);
|
||||
await Promise.all([journal.saveBalance(), journal.deleteEntries()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chanages the opening balance of the given contact.
|
||||
* @param {number} tenantId
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
* @param {ICustomerChangeOpeningBalanceDTO} changeOpeningBalance
|
||||
* @param {ICustomerChangeOpeningBalanceDTO} changeOpeningBalance
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async changeOpeningBalance(
|
||||
@@ -207,27 +252,34 @@ export default class ContactsService {
|
||||
contactId: number,
|
||||
contactService: string,
|
||||
openingBalance: number,
|
||||
openingBalanceAt?: Date|string,
|
||||
openingBalanceAt?: Date | string
|
||||
): Promise<void> {
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the given contact details or throw not found service error.
|
||||
const contact = await this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||
const contact = await this.getContactByIdOrThrowError(
|
||||
tenantId,
|
||||
contactId,
|
||||
contactService
|
||||
);
|
||||
|
||||
// Should the opening balance date be required.
|
||||
if (!contact.openingBalanceAt && !openingBalanceAt) {
|
||||
throw new ServiceError(ERRORS.OPENING_BALANCE_DATE_REQUIRED);
|
||||
};
|
||||
}
|
||||
// Changes the customer the opening balance and opening balance date.
|
||||
await contactRepository.update({
|
||||
openingBalance: openingBalance,
|
||||
await contactRepository.update(
|
||||
{
|
||||
openingBalance: openingBalance,
|
||||
|
||||
...(openingBalanceAt) && ({
|
||||
openingBalanceAt: moment(openingBalanceAt).toMySqlDateTime(),
|
||||
}),
|
||||
}, {
|
||||
id: contactId,
|
||||
contactService,
|
||||
});
|
||||
...(openingBalanceAt && {
|
||||
openingBalanceAt: moment(openingBalanceAt).toMySqlDateTime(),
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: contactId,
|
||||
contactService,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,11 +181,10 @@ export default class CustomersService {
|
||||
pagination: IPaginationMeta,
|
||||
filterMeta: IFilterMeta,
|
||||
}> {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const dynamicList = await this.dynamicListService.dynamicList(tenantId, Contact, customersFilter);
|
||||
const { Customer } = this.tenancy.models(tenantId);
|
||||
const dynamicList = await this.dynamicListService.dynamicList(tenantId, Customer, customersFilter);
|
||||
|
||||
const { results, pagination } = await Contact.query().onBuild((query) => {
|
||||
query.modify('customer');
|
||||
const { results, pagination } = await Customer.query().onBuild((query) => {
|
||||
dynamicList.buildQuery()(query);
|
||||
}).pagination(
|
||||
customersFilter.page - 1,
|
||||
|
||||
@@ -4,17 +4,17 @@ import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from 'decorators/eventDispatcher';
|
||||
import JournalPoster from "services/Accounting/JournalPoster";
|
||||
import JournalCommands from "services/Accounting/JournalCommands";
|
||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||
import ContactsService from 'services/Contacts/ContactsService';
|
||||
import {
|
||||
import {
|
||||
IVendorNewDTO,
|
||||
IVendorEditDTO,
|
||||
IVendor,
|
||||
IVendorsFilter,
|
||||
IPaginationMeta,
|
||||
IFilterMeta
|
||||
} from 'interfaces';
|
||||
IFilterMeta,
|
||||
} from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
@@ -39,10 +39,10 @@ export default class VendorsService {
|
||||
|
||||
/**
|
||||
* Converts vendor to contact DTO.
|
||||
* @param {IVendorNewDTO|IVendorEditDTO} vendorDTO
|
||||
* @param {IVendorNewDTO|IVendorEditDTO} vendorDTO
|
||||
* @returns {IContactDTO}
|
||||
*/
|
||||
private vendorToContactDTO(vendorDTO: IVendorNewDTO|IVendorEditDTO) {
|
||||
private vendorToContactDTO(vendorDTO: IVendorNewDTO | IVendorEditDTO) {
|
||||
return {
|
||||
...vendorDTO,
|
||||
active: defaultTo(vendorDTO.active, true),
|
||||
@@ -51,31 +51,49 @@ export default class VendorsService {
|
||||
|
||||
/**
|
||||
* Creates a new vendor.
|
||||
* @param {number} tenantId
|
||||
* @param {IVendorNewDTO} vendorDTO
|
||||
* @param {number} tenantId
|
||||
* @param {IVendorNewDTO} vendorDTO
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async newVendor(tenantId: number, vendorDTO: IVendorNewDTO) {
|
||||
this.logger.info('[vendor] trying create a new vendor.', { tenantId, vendorDTO });
|
||||
this.logger.info('[vendor] trying create a new vendor.', {
|
||||
tenantId,
|
||||
vendorDTO,
|
||||
});
|
||||
|
||||
const contactDTO = this.vendorToContactDTO(vendorDTO);
|
||||
const vendor = await this.contactService.newContact(tenantId, contactDTO, 'vendor');
|
||||
const vendor = await this.contactService.newContact(
|
||||
tenantId,
|
||||
contactDTO,
|
||||
'vendor'
|
||||
);
|
||||
|
||||
// Triggers `onVendorCreated` event.
|
||||
await this.eventDispatcher.dispatch(events.vendors.onCreated, {
|
||||
tenantId, vendorId: vendor.id, vendor,
|
||||
tenantId,
|
||||
vendorId: vendor.id,
|
||||
vendor,
|
||||
});
|
||||
return vendor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits details of the given vendor.
|
||||
* @param {number} tenantId
|
||||
* @param {IVendorEditDTO} vendorDTO
|
||||
* @param {number} tenantId
|
||||
* @param {IVendorEditDTO} vendorDTO
|
||||
*/
|
||||
public async editVendor(tenantId: number, vendorId: number, vendorDTO: IVendorEditDTO) {
|
||||
public async editVendor(
|
||||
tenantId: number,
|
||||
vendorId: number,
|
||||
vendorDTO: IVendorEditDTO
|
||||
) {
|
||||
const contactDTO = this.vendorToContactDTO(vendorDTO);
|
||||
const vendor = await this.contactService.editContact(tenantId, vendorId, contactDTO, 'vendor');
|
||||
const vendor = await this.contactService.editContact(
|
||||
tenantId,
|
||||
vendorId,
|
||||
contactDTO,
|
||||
'vendor'
|
||||
);
|
||||
|
||||
await this.eventDispatcher.dispatch(events.vendors.onEdited);
|
||||
|
||||
@@ -84,17 +102,21 @@ export default class VendorsService {
|
||||
|
||||
/**
|
||||
* Retrieve the given vendor details by id or throw not found.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
*/
|
||||
private getVendorByIdOrThrowError(tenantId: number, customerId: number) {
|
||||
return this.contactService.getContactByIdOrThrowError(tenantId, customerId, 'vendor');
|
||||
return this.contactService.getContactByIdOrThrowError(
|
||||
tenantId,
|
||||
customerId,
|
||||
'vendor'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given vendor from the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async deleteVendor(tenantId: number, vendorId: number) {
|
||||
@@ -103,17 +125,23 @@ export default class VendorsService {
|
||||
await this.getVendorByIdOrThrowError(tenantId, vendorId);
|
||||
await this.vendorHasNoBillsOrThrowError(tenantId, vendorId);
|
||||
|
||||
this.logger.info('[vendor] trying to delete vendor.', { tenantId, vendorId });
|
||||
this.logger.info('[vendor] trying to delete vendor.', {
|
||||
tenantId,
|
||||
vendorId,
|
||||
});
|
||||
await Contact.query().findById(vendorId).delete();
|
||||
|
||||
await this.eventDispatcher.dispatch(events.vendors.onDeleted, { tenantId, vendorId });
|
||||
await this.eventDispatcher.dispatch(events.vendors.onDeleted, {
|
||||
tenantId,
|
||||
vendorId,
|
||||
});
|
||||
this.logger.info('[vendor] deleted successfully.', { tenantId, vendorId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given vendor details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
*/
|
||||
public async getVendor(tenantId: number, vendorId: number) {
|
||||
return this.contactService.getContact(tenantId, vendorId, 'vendor');
|
||||
@@ -121,26 +149,26 @@ export default class VendorsService {
|
||||
|
||||
/**
|
||||
* Writes vendor opening balance journal entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @param {number} openingBalance
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @param {number} openingBalance
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async writeVendorOpeningBalanceJournal(
|
||||
tenantId: number,
|
||||
vendorId: number,
|
||||
openingBalance: number,
|
||||
openingBalance: number
|
||||
) {
|
||||
const journal = new JournalPoster(tenantId);
|
||||
const journalCommands = new JournalCommands(journal);
|
||||
|
||||
this.logger.info('[vendor] writing opening balance journal entries.', { tenantId, vendorId });
|
||||
await journalCommands.vendorOpeningBalance(vendorId, openingBalance)
|
||||
|
||||
await Promise.all([
|
||||
journal.saveBalance(),
|
||||
journal.saveEntries(),
|
||||
]);
|
||||
this.logger.info('[vendor] writing opening balance journal entries.', {
|
||||
tenantId,
|
||||
vendorId,
|
||||
});
|
||||
await journalCommands.vendorOpeningBalance(vendorId, openingBalance);
|
||||
|
||||
await Promise.all([journal.saveBalance(), journal.saveEntries()]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,28 +177,43 @@ export default class VendorsService {
|
||||
* @param {number} vendorId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async revertOpeningBalanceEntries(tenantId: number, vendorId: number|number[]) {
|
||||
public async revertOpeningBalanceEntries(
|
||||
tenantId: number,
|
||||
vendorId: number | number[]
|
||||
) {
|
||||
const id = Array.isArray(vendorId) ? vendorId : [vendorId];
|
||||
|
||||
this.logger.info('[customer] trying to revert opening balance journal entries.', { tenantId, customerId });
|
||||
this.logger.info(
|
||||
'[customer] trying to revert opening balance journal entries.',
|
||||
{ tenantId, customerId }
|
||||
);
|
||||
await this.contactService.revertJEntriesContactsOpeningBalance(
|
||||
tenantId, id, 'vendor',
|
||||
tenantId,
|
||||
id,
|
||||
'vendor'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given vendors or throw error if one of them not found.
|
||||
* @param {numebr} tenantId
|
||||
* @param {numebr} tenantId
|
||||
* @param {number[]} vendorsIds
|
||||
*/
|
||||
private getVendorsOrThrowErrorNotFound(tenantId: number, vendorsIds: number[]) {
|
||||
return this.contactService.getContactsOrThrowErrorNotFound(tenantId, vendorsIds, 'vendor');
|
||||
private getVendorsOrThrowErrorNotFound(
|
||||
tenantId: number,
|
||||
vendorsIds: number[]
|
||||
) {
|
||||
return this.contactService.getContactsOrThrowErrorNotFound(
|
||||
tenantId,
|
||||
vendorsIds,
|
||||
'vendor'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given vendors from the storage.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} vendorsIds
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} vendorsIds
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async deleteBulkVendors(
|
||||
@@ -183,38 +226,53 @@ export default class VendorsService {
|
||||
await this.vendorsHaveNoBillsOrThrowError(tenantId, vendorsIds);
|
||||
|
||||
await Contact.query().whereIn('id', vendorsIds).delete();
|
||||
await this.eventDispatcher.dispatch(events.vendors.onBulkDeleted, { tenantId, vendorsIds });
|
||||
await this.eventDispatcher.dispatch(events.vendors.onBulkDeleted, {
|
||||
tenantId,
|
||||
vendorsIds,
|
||||
});
|
||||
|
||||
this.logger.info('[vendor] bulk deleted successfully.', { tenantId, vendorsIds });
|
||||
this.logger.info('[vendor] bulk deleted successfully.', {
|
||||
tenantId,
|
||||
vendorsIds,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the vendor has no associated bills or throw service error.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
*/
|
||||
private async vendorHasNoBillsOrThrowError(tenantId: number, vendorId: number) {
|
||||
private async vendorHasNoBillsOrThrowError(
|
||||
tenantId: number,
|
||||
vendorId: number
|
||||
) {
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the bill that associated to the given vendor id.
|
||||
const bills = await billRepository.find({ vendor_id: vendorId });
|
||||
|
||||
if (bills.length > 0) {
|
||||
throw new ServiceError('vendor_has_bills')
|
||||
throw new ServiceError('vendor_has_bills');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws error in case one of vendors have associated bills.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} customersIds
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} customersIds
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
private async vendorsHaveNoBillsOrThrowError(tenantId: number, vendorsIds: number[]) {
|
||||
private async vendorsHaveNoBillsOrThrowError(
|
||||
tenantId: number,
|
||||
vendorsIds: number[]
|
||||
) {
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieves bills that assocaited to the given vendors.
|
||||
const vendorsBills = await billRepository.findWhereIn('vendor_id', vendorsIds);
|
||||
const vendorsBills = await billRepository.findWhereIn(
|
||||
'vendor_id',
|
||||
vendorsIds
|
||||
);
|
||||
const billsVendorsIds = vendorsBills.map((bill) => bill.vendorId);
|
||||
|
||||
// The difference between the vendors ids and bills vendors ids.
|
||||
@@ -233,18 +291,24 @@ export default class VendorsService {
|
||||
public async getVendorsList(
|
||||
tenantId: number,
|
||||
vendorsFilter: IVendorsFilter
|
||||
): Promise<{ vendors: IVendor[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, Contact, vendorsFilter);
|
||||
|
||||
const { results, pagination } = await Contact.query().onBuild((builder) => {
|
||||
builder.modify('vendor');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
}).pagination(
|
||||
vendorsFilter.page - 1,
|
||||
vendorsFilter.pageSize,
|
||||
): Promise<{
|
||||
vendors: IVendor[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { Vendor } = this.tenancy.models(tenantId);
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Vendor,
|
||||
vendorsFilter
|
||||
);
|
||||
|
||||
const { results, pagination } = await Vendor.query()
|
||||
.onBuild((builder) => {
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(vendorsFilter.page - 1, vendorsFilter.pageSize);
|
||||
|
||||
return {
|
||||
vendors: results,
|
||||
pagination,
|
||||
@@ -254,28 +318,33 @@ export default class VendorsService {
|
||||
|
||||
/**
|
||||
* Changes the opeing balance of the given vendor.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @param {number} openingBalance
|
||||
* @param {Date|string} openingBalanceAt
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @param {number} openingBalance
|
||||
* @param {Date|string} openingBalanceAt
|
||||
*/
|
||||
public async changeOpeningBalance(
|
||||
tenantId: number,
|
||||
vendorId: number,
|
||||
openingBalance: number,
|
||||
openingBalanceAt: Date|string,
|
||||
openingBalanceAt: Date | string
|
||||
): Promise<void> {
|
||||
|
||||
await this.contactService.changeOpeningBalance(
|
||||
tenantId,
|
||||
vendorId,
|
||||
'vendor',
|
||||
openingBalance,
|
||||
openingBalanceAt,
|
||||
openingBalanceAt
|
||||
);
|
||||
// Triggers `onOpeingBalanceChanged` event.
|
||||
await this.eventDispatcher.dispatch(events.vendors.onOpeningBalanceChanged, {
|
||||
tenantId, vendorId, openingBalance, openingBalanceAt
|
||||
});
|
||||
await this.eventDispatcher.dispatch(
|
||||
events.vendors.onOpeningBalanceChanged,
|
||||
{
|
||||
tenantId,
|
||||
vendorId,
|
||||
openingBalance,
|
||||
openingBalanceAt,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
ICurrencyEditDTO,
|
||||
ICurrencyDTO,
|
||||
ICurrenciesService,
|
||||
ICurrency
|
||||
ICurrency,
|
||||
} from 'interfaces';
|
||||
import {
|
||||
EventDispatcher,
|
||||
@@ -14,7 +14,7 @@ import TenancyService from 'services/Tenancy/TenancyService';
|
||||
|
||||
const ERRORS = {
|
||||
CURRENCY_NOT_FOUND: 'currency_not_found',
|
||||
CURRENCY_CODE_EXISTS: 'currency_code_exists'
|
||||
CURRENCY_CODE_EXISTS: 'currency_code_exists',
|
||||
};
|
||||
|
||||
@Service()
|
||||
@@ -30,40 +30,62 @@ export default class CurrenciesService implements ICurrenciesService {
|
||||
|
||||
/**
|
||||
* Retrieve currency by given currency code or throw not found error.
|
||||
* @param {number} tenantId
|
||||
* @param {string} currencyCode
|
||||
* @param {number} currencyId
|
||||
* @param {number} tenantId
|
||||
* @param {string} currencyCode
|
||||
* @param {number} currencyId
|
||||
*/
|
||||
private async validateCurrencyCodeUniquiness(tenantId: number, currencyCode: string, currencyId?: number) {
|
||||
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 });
|
||||
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)
|
||||
query.whereNot('id', currencyId);
|
||||
}
|
||||
});
|
||||
if (foundCurrency) {
|
||||
this.logger.info('[currencies] the currency code already exists.', { tenantId, currencyCode });
|
||||
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
|
||||
* @param {number} tenantId
|
||||
* @param {string} currencyCode
|
||||
*/
|
||||
private async getCurrencyByCodeOrThrowError(tenantId: number, currencyCode: string) {
|
||||
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);
|
||||
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 });
|
||||
this.logger.info('[currencies] the given currency code not exists.', {
|
||||
tenantId,
|
||||
currencyCode,
|
||||
});
|
||||
throw new ServiceError(ERRORS.CURRENCY_NOT_FOUND);
|
||||
}
|
||||
return foundCurrency;
|
||||
@@ -71,17 +93,26 @@ export default class CurrenciesService implements ICurrenciesService {
|
||||
|
||||
/**
|
||||
* Retrieve currency by given id or throw not found error.
|
||||
* @param {number} tenantId
|
||||
* @param {number} currencyId
|
||||
* @param {number} tenantId
|
||||
* @param {number} currencyId
|
||||
*/
|
||||
private async getCurrencyIdOrThrowError(tenantId: number, currencyId: number) {
|
||||
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 });
|
||||
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 });
|
||||
this.logger.info('[currencies] the currency code not found.', {
|
||||
tenantId,
|
||||
currencyId,
|
||||
});
|
||||
throw new ServiceError(ERRORS.CURRENCY_NOT_FOUND);
|
||||
}
|
||||
return foundCurrency;
|
||||
@@ -89,56 +120,87 @@ export default class CurrenciesService implements ICurrenciesService {
|
||||
|
||||
/**
|
||||
* Creates a new currency.
|
||||
* @param {number} tenantId
|
||||
* @param {ICurrencyDTO} currencyDTO
|
||||
* @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 });
|
||||
this.logger.info('[currencies] try to insert new currency.', {
|
||||
tenantId,
|
||||
currencyDTO,
|
||||
});
|
||||
|
||||
await this.validateCurrencyCodeUniquiness(tenantId, currencyDTO.currencyCode);
|
||||
await this.validateCurrencyCodeUniquiness(
|
||||
tenantId,
|
||||
currencyDTO.currencyCode
|
||||
);
|
||||
|
||||
await Currency.query().insert({ ...currencyDTO });
|
||||
this.logger.info('[currencies] the currency inserted successfully.', { tenantId, 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
|
||||
* @param {number} tenantId
|
||||
* @param {number} currencyId
|
||||
* @param {ICurrencyDTO} currencyDTO
|
||||
*/
|
||||
public async editCurrency(tenantId: number, currencyId: number, currencyDTO: ICurrencyEditDTO): Promise<ICurrency> {
|
||||
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 });
|
||||
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 });
|
||||
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
|
||||
* @param {number} tenantId
|
||||
* @param {string} currencyCode
|
||||
* @return {Promise<}
|
||||
*/
|
||||
public async deleteCurrency(tenantId: number, currencyCode: string): Promise<void> {
|
||||
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 });
|
||||
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 });
|
||||
this.logger.info('[currencies] the currency deleted successfully.', {
|
||||
tenantId,
|
||||
currencyCode,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listing currencies.
|
||||
* @param {number} tenantId
|
||||
* @param {number} tenantId
|
||||
* @return {Promise<ICurrency[]>}
|
||||
*/
|
||||
public async listCurrencies(tenantId: number): Promise<ICurrency[]> {
|
||||
@@ -149,4 +211,4 @@ export default class CurrenciesService implements ICurrenciesService {
|
||||
});
|
||||
return currencies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
import { IInviteUserInput, ISystemUser } from "interfaces";
|
||||
import Mail from "lib/Mail";
|
||||
import { Service } from "typedi";
|
||||
import { ISystemUser } from 'interfaces';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import Mail from 'lib/Mail';
|
||||
import { Service, Container } from 'typedi';
|
||||
import config from 'config';
|
||||
|
||||
@Service()
|
||||
export default class InviteUsersMailMessages {
|
||||
|
||||
/**
|
||||
* Sends invite mail to the given email.
|
||||
* @param user
|
||||
* @param invite
|
||||
* @param user
|
||||
* @param invite
|
||||
*/
|
||||
async sendInviteMail(user: ISystemUser, invite) {
|
||||
async sendInviteMail(tenantId: number, fromUser: ISystemUser, invite: any) {
|
||||
const { protocol, hostname } = config;
|
||||
const tenancyService = Container.get(TenancyService);
|
||||
|
||||
// Retrieve tenant's settings
|
||||
const settings = tenancyService.settings(tenantId);
|
||||
|
||||
// Retreive tenant orgnaization name.
|
||||
const organizationName = settings.get({
|
||||
group: 'organization',
|
||||
key: 'name',
|
||||
});
|
||||
const mail = new Mail()
|
||||
.setSubject(`${user.fullName} has invited you to join a Bigcapital`)
|
||||
.setSubject(`${fromUser.firstName} has invited you to join a Bigcapital`)
|
||||
.setView('mail/UserInvite.html')
|
||||
.setTo(invite.email)
|
||||
.setData({
|
||||
acceptUrl: `${req.protocol}://${req.hostname}/invite/accept/${invite.token}`,
|
||||
fullName: `${user.firstName} ${user.lastName}`,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.email,
|
||||
organizationName: organizationOptions.getMeta('organization_name'),
|
||||
acceptUrl: `${protocol}://${hostname}/invite/accept/${invite.token}`,
|
||||
fullName: `${fromUser.firstName} ${fromUser.lastName}`,
|
||||
firstName: fromUser.firstName,
|
||||
lastName: fromUser.lastName,
|
||||
email: fromUser.email,
|
||||
organizationName,
|
||||
});
|
||||
|
||||
|
||||
await mail.send();
|
||||
Logger.log('info', 'User has been sent invite user email successfuly.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,23 +42,27 @@ export default class InviteUserService {
|
||||
token: string,
|
||||
inviteUserInput: IInviteUserInput
|
||||
): Promise<void> {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
|
||||
// Retrieve the invite token or throw not found error.
|
||||
const inviteToken = await this.getInviteOrThrowError(token);
|
||||
|
||||
// Validates the user phone number.
|
||||
await this.validateUserPhoneNumber(inviteUserInput);
|
||||
|
||||
this.logger.info('[aceept_invite] trying to hash the user password.');
|
||||
const hashedPassword = await hashPassword(inviteUserInput.password);
|
||||
|
||||
this.logger.info('[accept_invite] trying to update user details.');
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
const user = await systemUserRepository.findOneByEmail(inviteToken.email);
|
||||
|
||||
const user = await systemUserRepository.getByEmail(inviteToken.email);
|
||||
|
||||
const updateUserOper = systemUserRepository.edit(user.id, {
|
||||
// Sets the invited user details after invite accepting.
|
||||
const updateUserOper = systemUserRepository.update({
|
||||
...inviteUserInput,
|
||||
active: 1,
|
||||
invite_accepted_at: moment().format('YYYY-MM-DD'),
|
||||
inviteAcceptedAt: moment().format('YYYY-MM-DD'),
|
||||
password: hashedPassword,
|
||||
});
|
||||
}, { id: user.id });
|
||||
|
||||
this.logger.info('[accept_invite] trying to delete the given token.');
|
||||
const deleteInviteTokenOper = Invite.query()
|
||||
@@ -70,7 +74,6 @@ export default class InviteUserService {
|
||||
updateUserOper,
|
||||
deleteInviteTokenOper,
|
||||
]);
|
||||
|
||||
// Triggers `onUserAcceptInvite` event.
|
||||
this.eventDispatcher.dispatch(events.inviteUser.acceptInvite, {
|
||||
inviteToken,
|
||||
@@ -94,6 +97,9 @@ export default class InviteUserService {
|
||||
invite: IInvite,
|
||||
user: ISystemUser
|
||||
}> {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
|
||||
// Throw error in case user email exists.
|
||||
await this.throwErrorIfUserEmailExists(email);
|
||||
|
||||
this.logger.info('[send_invite] trying to store invite token.');
|
||||
@@ -106,16 +112,14 @@ export default class InviteUserService {
|
||||
this.logger.info(
|
||||
'[send_invite] trying to store user with email and tenant.'
|
||||
);
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
const user = await systemUserRepository.create({
|
||||
email,
|
||||
tenant_id: authorizedUser.tenantId,
|
||||
active: 1,
|
||||
});
|
||||
|
||||
// Triggers `onUserSendInvite` event.
|
||||
this.eventDispatcher.dispatch(events.inviteUser.sendInvite, {
|
||||
invite,
|
||||
invite, authorizedUser, tenantId
|
||||
});
|
||||
return { invite, user };
|
||||
}
|
||||
@@ -155,7 +159,7 @@ export default class InviteUserService {
|
||||
email: string
|
||||
): Promise<ISystemUser> {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
const foundUser = await systemUserRepository.getByEmail(email);
|
||||
const foundUser = await systemUserRepository.findOneByEmail(email);
|
||||
|
||||
if (foundUser) {
|
||||
throw new ServiceError('email_already_invited');
|
||||
@@ -187,7 +191,7 @@ export default class InviteUserService {
|
||||
inviteUserInput: IInviteUserInput
|
||||
): Promise<ISystemUser> {
|
||||
const { systemUserRepository } = this.sysRepositories;
|
||||
const foundUser = await systemUserRepository.getByPhoneNumber(
|
||||
const foundUser = await systemUserRepository.findOneByPhoneNumber(
|
||||
inviteUserInput.phoneNumber
|
||||
);
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ export default class MediaService implements IMediaService {
|
||||
|
||||
const mediaIds = Array.isArray(mediaId) ? mediaId : [mediaId];
|
||||
|
||||
const tenant = await tenantRepository.getById(tenantId);
|
||||
const tenant = await tenantRepository.findOneById(tenantId);
|
||||
const media = await this.getMediaByIdsOrThrowError(tenantId, mediaIds);
|
||||
|
||||
const tenantPath = `${publicPath}${tenant.organizationId}`;
|
||||
@@ -192,7 +192,7 @@ export default class MediaService implements IMediaService {
|
||||
|
||||
this.logger.info('[media] trying to upload media.', { tenantId });
|
||||
|
||||
const tenant = await tenantRepository.getById(tenantId);
|
||||
const tenant = await tenantRepository.findOneById(tenantId);
|
||||
const fileName = `${attachment.md5}.png`;
|
||||
|
||||
// Validate the attachment.
|
||||
|
||||
@@ -106,7 +106,7 @@ export default class OrganizationService {
|
||||
});
|
||||
|
||||
const { tenantRepository } = this.sysRepositories;
|
||||
const tenant = await tenantRepository.getById(user.tenantId);
|
||||
const tenant = await tenantRepository.findOneById(user.tenantId);
|
||||
|
||||
return [tenant];
|
||||
}
|
||||
@@ -150,7 +150,8 @@ export default class OrganizationService {
|
||||
*/
|
||||
private async getTenantByOrgIdOrThrowError(organizationId: string) {
|
||||
const { tenantRepository } = this.sysRepositories;
|
||||
const tenant = await tenantRepository.getByOrgId(organizationId);
|
||||
const tenant = await tenantRepository.findOne({ organizationId });
|
||||
|
||||
this.throwIfTenantNotExists(tenant);
|
||||
|
||||
return tenant;
|
||||
|
||||
@@ -28,14 +28,13 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import CustomersService from 'services/Contacts/CustomersService';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
|
||||
|
||||
const ERRORS = {
|
||||
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
||||
SALE_INVOICE_ALREADY_DELIVERED: 'SALE_INVOICE_ALREADY_DELIVERED',
|
||||
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
||||
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE'
|
||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -72,13 +71,20 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
saleEstimatesService: SaleEstimateService;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Validate whether sale invoice number unqiue on the storage.
|
||||
*/
|
||||
async validateInvoiceNumberUnique(tenantId: number, invoiceNumber: string, notInvoiceId?: number) {
|
||||
async validateInvoiceNumberUnique(
|
||||
tenantId: number,
|
||||
invoiceNumber: string,
|
||||
notInvoiceId?: number
|
||||
) {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[sale_invoice] validating sale invoice number existance.', { tenantId, invoiceNumber });
|
||||
|
||||
this.logger.info(
|
||||
'[sale_invoice] validating sale invoice number existance.',
|
||||
{ tenantId, invoiceNumber }
|
||||
);
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.findOne('invoice_no', invoiceNumber)
|
||||
.onBuild((builder) => {
|
||||
@@ -88,8 +94,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
});
|
||||
|
||||
if (saleInvoice) {
|
||||
this.logger.info('[sale_invoice] sale invoice number not unique.', { tenantId, invoiceNumber });
|
||||
throw new ServiceError(ERRORS.INVOICE_NUMBER_NOT_UNIQUE)
|
||||
this.logger.info('[sale_invoice] sale invoice number not unique.', {
|
||||
tenantId,
|
||||
invoiceNumber,
|
||||
});
|
||||
throw new ServiceError(ERRORS.INVOICE_NUMBER_NOT_UNIQUE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,73 +125,91 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
*/
|
||||
transformDTOToModel(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO|ISaleInvoiceEditDTO,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO,
|
||||
oldSaleInvoice?: ISaleInvoice
|
||||
): ISaleInvoice {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
const balance = sumBy(saleInvoiceDTO.entries, (e) =>
|
||||
ItemEntry.calcAmount(e)
|
||||
);
|
||||
|
||||
return {
|
||||
...formatDateFields(
|
||||
omit(saleInvoiceDTO, ['delivered']),
|
||||
['invoiceDate', 'dueDate']
|
||||
),
|
||||
...formatDateFields(omit(saleInvoiceDTO, ['delivered', 'entries']), [
|
||||
'invoiceDate',
|
||||
'dueDate',
|
||||
]),
|
||||
// Avoid rewrite the deliver date in edit mode when already published.
|
||||
...(saleInvoiceDTO.delivered && (!oldSaleInvoice?.deliveredAt)) && ({
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
...(saleInvoiceDTO.delivered &&
|
||||
!oldSaleInvoice?.deliveredAt && {
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
balance,
|
||||
paymentAmount: 0,
|
||||
}
|
||||
entries: saleInvoiceObj.entries.map((entry) => ({
|
||||
reference_type: 'SaleInvoice',
|
||||
...omit(entry, ['amount', 'id']),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sale invoices and store it to the storage
|
||||
* with associated to entries and journal transactions.
|
||||
* @async
|
||||
* @param {number} tenantId =
|
||||
* @param {ISaleInvoice} saleInvoiceDTO -
|
||||
* @param {number} tenantId =
|
||||
* @param {ISaleInvoice} saleInvoiceDTO -
|
||||
* @return {ISaleInvoice}
|
||||
*/
|
||||
public async createSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const invLotNumber = 1;
|
||||
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO);
|
||||
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||
await this.customersService.getCustomerByIdOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceDTO.customerId
|
||||
);
|
||||
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceDTO.invoiceNo) {
|
||||
await this.validateInvoiceNumberUnique(tenantId, saleInvoiceDTO.invoiceNo);
|
||||
await this.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceDTO.invoiceNo
|
||||
);
|
||||
}
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
|
||||
// Validate items should be sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.insertGraphAndFetch({
|
||||
...omit(saleInvoiceObj, ['entries']),
|
||||
|
||||
entries: saleInvoiceObj.entries.map((entry) => ({
|
||||
reference_type: 'SaleInvoice',
|
||||
...omit(entry, ['amount', 'id']),
|
||||
}))
|
||||
});
|
||||
const saleInvoice = await saleInvoiceRepository.upsertGraph({
|
||||
...saleInvoiceObj,
|
||||
});
|
||||
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, {
|
||||
tenantId, saleInvoice, saleInvoiceId: saleInvoice.id,
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
saleInvoiceId: saleInvoice.id,
|
||||
});
|
||||
this.logger.info('[sale_invoice] successfully inserted.', {
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
});
|
||||
this.logger.info('[sale_invoice] successfully inserted.', { tenantId, saleInvoice });
|
||||
|
||||
return saleInvoice;
|
||||
}
|
||||
@@ -190,50 +217,85 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
/**
|
||||
* Edit the given sale invoice.
|
||||
* @async
|
||||
* @param {number} tenantId -
|
||||
* @param {number} tenantId -
|
||||
* @param {Number} saleInvoiceId -
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
*/
|
||||
public async editSaleInvoice(tenantId: number, saleInvoiceId: number, saleInvoiceDTO: any): Promise<ISaleInvoice> {
|
||||
public async editSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
saleInvoiceDTO: any
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
const balance = sumBy(saleInvoiceDTO.entries, (e) =>
|
||||
ItemEntry.calcAmount(e)
|
||||
);
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO, oldSaleInvoice);
|
||||
const saleInvoiceObj = this.transformDTOToModel(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
oldSaleInvoice
|
||||
);
|
||||
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||
await this.customersService.getCustomerByIdOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceDTO.customerId
|
||||
);
|
||||
|
||||
// Validate sale invoice number uniquiness.
|
||||
if (saleInvoiceDTO.invoiceNo) {
|
||||
await this.validateInvoiceNumberUnique(tenantId, saleInvoiceDTO.invoiceNo, saleInvoiceId);
|
||||
await this.validateInvoiceNumberUnique(
|
||||
tenantId,
|
||||
saleInvoiceDTO.invoiceNo,
|
||||
saleInvoiceId
|
||||
);
|
||||
}
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
|
||||
// Validate non-sellable entries items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
|
||||
// Validate the items entries existance.
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, saleInvoiceId, 'SaleInvoice', saleInvoiceDTO.entries);
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
'SaleInvoice',
|
||||
saleInvoiceDTO.entries
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] trying to update sale invoice.');
|
||||
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
|
||||
.upsertGraphAndFetch({
|
||||
const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch(
|
||||
{
|
||||
id: saleInvoiceId,
|
||||
...omit(saleInvoiceObj, ['entries', 'invLotNumber']),
|
||||
|
||||
entries: saleInvoiceObj.entries.map((entry) => ({
|
||||
reference_type: 'SaleInvoice',
|
||||
...omit(entry, ['amount']),
|
||||
}))
|
||||
});
|
||||
})),
|
||||
}
|
||||
);
|
||||
|
||||
// Triggers `onSaleInvoiceEdited` event.
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
|
||||
saleInvoice, oldSaleInvoice, tenantId, saleInvoiceId,
|
||||
saleInvoice,
|
||||
oldSaleInvoice,
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
});
|
||||
return saleInvoice;
|
||||
}
|
||||
@@ -246,21 +308,27 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
*/
|
||||
public async deliverSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
saleInvoiceId: number
|
||||
): Promise<void> {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve details of the given sale invoice id.
|
||||
const saleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
const saleInvoice = await this.getInvoiceOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
|
||||
// Throws error in case the sale invoice already published.
|
||||
if (saleInvoice.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Record the delivered at on the storage.
|
||||
await saleInvoiceRepository.update({
|
||||
deliveredAt: moment().toMySqlDateTime()
|
||||
}, { id: saleInvoiceId });
|
||||
await saleInvoiceRepository.update(
|
||||
{
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
},
|
||||
{ id: saleInvoiceId }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,15 +337,21 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
* @async
|
||||
* @param {Number} saleInvoiceId - The given sale invoice id.
|
||||
*/
|
||||
public async deleteSaleInvoice(tenantId: number, saleInvoiceId: number): Promise<void> {
|
||||
public async deleteSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number
|
||||
): Promise<void> {
|
||||
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(
|
||||
tenantId,
|
||||
saleInvoiceId
|
||||
);
|
||||
|
||||
// Unlink the converted sale estimates from the given sale invoice.
|
||||
await this.saleEstimatesService.unlinkConvertedEstimateFromInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
saleInvoiceId
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] delete sale invoice with entries.');
|
||||
@@ -288,7 +362,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
.delete();
|
||||
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onDeleted, {
|
||||
tenantId, oldSaleInvoice,
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,44 +378,48 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
saleInvoice,
|
||||
saleInvoiceId: number,
|
||||
override?: boolean
|
||||
){
|
||||
) {
|
||||
this.logger.info('[sale_invoice] saving inventory transactions');
|
||||
const inventortyTransactions = saleInvoice.entries
|
||||
.map((entry) => ({
|
||||
...pick(entry, ['item_id', 'quantity', 'rate',]),
|
||||
lotNumber: saleInvoice.invLotNumber,
|
||||
transactionType: 'SaleInvoice',
|
||||
transactionId: saleInvoiceId,
|
||||
direction: 'OUT',
|
||||
date: saleInvoice.invoice_date,
|
||||
entryId: entry.id,
|
||||
}));
|
||||
const inventortyTransactions = saleInvoice.entries.map((entry) => ({
|
||||
...pick(entry, ['item_id', 'quantity', 'rate']),
|
||||
lotNumber: saleInvoice.invLotNumber,
|
||||
transactionType: 'SaleInvoice',
|
||||
transactionId: saleInvoiceId,
|
||||
direction: 'OUT',
|
||||
date: saleInvoice.invoice_date,
|
||||
entryId: entry.id,
|
||||
}));
|
||||
|
||||
return this.inventoryService.recordInventoryTransactions(
|
||||
tenantId, inventortyTransactions, override,
|
||||
tenantId,
|
||||
inventortyTransactions,
|
||||
override
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the inventory transactions.
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
*/
|
||||
private async revertInventoryTransactions(tenantId: number, inventoryTransactions: array) {
|
||||
private async revertInventoryTransactions(
|
||||
tenantId: number,
|
||||
inventoryTransactions: array
|
||||
) {
|
||||
const { InventoryTransaction } = this.tenancy.models(tenantId);
|
||||
const opers: Promise<[]>[] = [];
|
||||
|
||||
|
||||
this.logger.info('[sale_invoice] reverting inventory transactions');
|
||||
|
||||
inventoryTransactions.forEach((trans: any) => {
|
||||
switch(trans.direction) {
|
||||
switch (trans.direction) {
|
||||
case 'OUT':
|
||||
if (trans.inventoryTransactionId) {
|
||||
const revertRemaining = InventoryTransaction.query()
|
||||
.where('id', trans.inventoryTransactionId)
|
||||
.where('direction', 'OUT')
|
||||
.increment('remaining', trans.quanitity);
|
||||
|
||||
|
||||
opers.push(revertRemaining);
|
||||
}
|
||||
break;
|
||||
@@ -361,16 +440,19 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
/**
|
||||
* Retrieve sale invoice with associated entries.
|
||||
* @async
|
||||
* @param {Number} saleInvoiceId
|
||||
* @param {Number} saleInvoiceId
|
||||
*/
|
||||
public async getSaleInvoice(tenantId: number, saleInvoiceId: number): Promise<ISaleInvoice> {
|
||||
public async getSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const saleInvoice = await SaleInvoice.query()
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphFetched('entries')
|
||||
.withGraphFetched('customer');
|
||||
|
||||
|
||||
if (!saleInvoice) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND);
|
||||
}
|
||||
@@ -378,9 +460,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules compute sale invoice items cost based on each item
|
||||
* Schedules compute sale invoice items cost based on each item
|
||||
* cost method.
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @param {ISaleInvoice} saleInvoice
|
||||
* @return {Promise}
|
||||
*/
|
||||
async scheduleComputeInvoiceItemsCost(
|
||||
@@ -396,15 +478,20 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
const inventoryItemsIds = chain(saleInvoice.entries)
|
||||
.filter((entry: IItemEntry) => entry.item.type === 'inventory')
|
||||
.map((entry: IItemEntry) => entry.itemId)
|
||||
.uniq().value();
|
||||
.uniq()
|
||||
.value();
|
||||
|
||||
if (inventoryItemsIds.length === 0) {
|
||||
await this.writeNonInventoryInvoiceJournals(tenantId, saleInvoice, override);
|
||||
await this.writeNonInventoryInvoiceJournals(
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
override
|
||||
);
|
||||
} else {
|
||||
await this.scheduleComputeItemsCost(
|
||||
tenantId,
|
||||
inventoryItemsIds,
|
||||
saleInvoice.invoice_date,
|
||||
saleInvoice.invoice_date
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -436,40 +523,43 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
await Promise.all([
|
||||
journal.deleteEntries(),
|
||||
journal.saveEntries(),
|
||||
journal.saveBalance(),
|
||||
journal.saveBalance(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sales invoices filterable and paginated list.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
public async salesInvoicesList(
|
||||
tenantId: number,
|
||||
salesInvoicesFilter: ISalesInvoicesFilter
|
||||
): Promise<{
|
||||
salesInvoices: ISaleInvoice[],
|
||||
pagination: IPaginationMeta,
|
||||
filterMeta: IFilterMeta
|
||||
salesInvoices: ISaleInvoice[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleInvoice, salesInvoicesFilter);
|
||||
|
||||
this.logger.info('[sale_invoice] try to get sales invoices list.', { tenantId, salesInvoicesFilter });
|
||||
const {
|
||||
results,
|
||||
pagination,
|
||||
} = await SaleInvoice.query().onBuild((builder) => {
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('customer');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
}).pagination(
|
||||
salesInvoicesFilter.page - 1,
|
||||
salesInvoicesFilter.pageSize,
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
SaleInvoice,
|
||||
salesInvoicesFilter
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] try to get sales invoices list.', {
|
||||
tenantId,
|
||||
salesInvoicesFilter,
|
||||
});
|
||||
const { results, pagination } = await SaleInvoice.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('customer');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(salesInvoicesFilter.page - 1, salesInvoicesFilter.pageSize);
|
||||
|
||||
return {
|
||||
salesInvoices: results,
|
||||
pagination,
|
||||
@@ -479,12 +569,12 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
|
||||
/**
|
||||
* Retrieve due sales invoices.
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
* @param {number} tenantId
|
||||
* @param {number} customerId
|
||||
*/
|
||||
public async getPayableInvoices(
|
||||
tenantId: number,
|
||||
customerId?: number,
|
||||
customerId?: number
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class SubscriptionService {
|
||||
const { tenantRepository } = this.sysRepositories;
|
||||
|
||||
const plan = await Plan.query().findOne('slug', planSlug);
|
||||
const tenant = await tenantRepository.getById(tenantId);
|
||||
const tenant = await tenantRepository.findOneById(tenantId);
|
||||
|
||||
const paymentViaLicense = new LicensePaymentMethod();
|
||||
const paymentContext = new PaymentContext(paymentViaLicense);
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class TenantsManagerService implements ITenantManager{
|
||||
*/
|
||||
public async createTenant(): Promise<ITenant> {
|
||||
const { tenantRepository } = this.sysRepositories;
|
||||
const tenant = await tenantRepository.newTenantWithUniqueOrgId();
|
||||
const tenant = await tenantRepository.createWithUniqueOrgId();
|
||||
|
||||
return tenant;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Inject, Service } from "typedi";
|
||||
import { Inject, Service } from 'typedi';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { SystemUser } from "system/models";
|
||||
import { ServiceError, ServiceErrors } from "exceptions";
|
||||
import { ISystemUser, ISystemUserDTO } from "interfaces";
|
||||
import systemRepositories from "loaders/systemRepositories";
|
||||
import { SystemUser } from 'system/models';
|
||||
import { ServiceError, ServiceErrors } from 'exceptions';
|
||||
import { ISystemUser, ISystemUserDTO } from 'interfaces';
|
||||
import systemRepositories from 'loaders/systemRepositories';
|
||||
|
||||
@Service()
|
||||
export default class UsersService {
|
||||
@@ -18,47 +18,64 @@ export default class UsersService {
|
||||
|
||||
/**
|
||||
* Creates a new user.
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
* @param {IUserDTO} userDTO
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
* @param {IUserDTO} userDTO
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
async editUser(tenantId: number, userId: number, userDTO: ISystemUserDTO): Promise<ISystemUser> {
|
||||
async editUser(
|
||||
tenantId: number,
|
||||
userId: number,
|
||||
userDTO: ISystemUserDTO
|
||||
): Promise<ISystemUser> {
|
||||
const { systemUserRepository } = this.repositories;
|
||||
|
||||
const isEmailExists = await systemUserRepository.isEmailExists(userDTO.email, userId);
|
||||
const isPhoneNumberExists = await systemUserRepository.isPhoneNumberExists(userDTO.phoneNumber, userId);
|
||||
|
||||
const userByEmail = await systemUserRepository.findOne({
|
||||
email: userDTO.email,
|
||||
id: userId,
|
||||
});
|
||||
const userByPhoneNumber = await systemUserRepository.findOne({
|
||||
phoneNumber: userDTO.phoneNumber,
|
||||
id: userId
|
||||
});
|
||||
const serviceErrors: ServiceError[] = [];
|
||||
|
||||
if (isEmailExists) {
|
||||
if (userByEmail) {
|
||||
serviceErrors.push(new ServiceError('email_already_exists'));
|
||||
}
|
||||
if (isPhoneNumberExists) {
|
||||
if (userByPhoneNumber) {
|
||||
serviceErrors.push(new ServiceError('phone_number_already_exist'));
|
||||
}
|
||||
if (serviceErrors.length > 0) {
|
||||
throw new ServiceErrors(serviceErrors);
|
||||
}
|
||||
const updateSystemUser = await SystemUser.query()
|
||||
.where('id', userId)
|
||||
.update({ ...userDTO });
|
||||
const updateSystemUser = await systemUserRepository
|
||||
.update({ ...userDTO, }, { id: userId });
|
||||
|
||||
return updateSystemUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate user existance throw error in case user was not found.,
|
||||
* @param {number} tenantId -
|
||||
* @param {number} tenantId -
|
||||
* @param {number} userId -
|
||||
* @returns {ISystemUser}
|
||||
*/
|
||||
async getUserOrThrowError(tenantId: number, userId: number): Promise<ISystemUser> {
|
||||
async getUserOrThrowError(
|
||||
tenantId: number,
|
||||
userId: number
|
||||
): Promise<ISystemUser> {
|
||||
const { systemUserRepository } = this.repositories;
|
||||
const user = await systemUserRepository.getByIdAndTenant(userId, tenantId);
|
||||
const user = await systemUserRepository.findOneByIdAndTenant(
|
||||
userId,
|
||||
tenantId
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
this.logger.info('[users] the given user not found.', { tenantId, userId });
|
||||
this.logger.info('[users] the given user not found.', {
|
||||
tenantId,
|
||||
userId,
|
||||
});
|
||||
throw new ServiceError('user_not_found');
|
||||
}
|
||||
return user;
|
||||
@@ -66,25 +83,35 @@ export default class UsersService {
|
||||
|
||||
/**
|
||||
* Deletes the given user id.
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
*/
|
||||
async deleteUser(tenantId: number, userId: number): Promise<void> {
|
||||
const { systemUserRepository } = this.repositories;
|
||||
await this.getUserOrThrowError(tenantId, userId);
|
||||
|
||||
this.logger.info('[users] trying to delete the given user.', { tenantId, userId });
|
||||
this.logger.info('[users] trying to delete the given user.', {
|
||||
tenantId,
|
||||
userId,
|
||||
});
|
||||
await systemUserRepository.deleteById(userId);
|
||||
|
||||
this.logger.info('[users] the given user deleted successfully.', { tenantId, userId });
|
||||
|
||||
this.logger.info('[users] the given user deleted successfully.', {
|
||||
tenantId,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the given user id.
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
*/
|
||||
async activateUser(tenantId: number, userId: number, authorizedUser: ISystemUser): Promise<void> {
|
||||
async activateUser(
|
||||
tenantId: number,
|
||||
userId: number,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<void> {
|
||||
this.throwErrorIfUserIdSameAuthorizedUser(userId, authorizedUser);
|
||||
const { systemUserRepository } = this.repositories;
|
||||
|
||||
@@ -96,11 +123,15 @@ export default class UsersService {
|
||||
|
||||
/**
|
||||
* Inactivate the given user id.
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
* @param {number} tenantId
|
||||
* @param {number} userId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async inactivateUser(tenantId: number, userId: number, authorizedUser: ISystemUser): Promise<void> {
|
||||
async inactivateUser(
|
||||
tenantId: number,
|
||||
userId: number,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<void> {
|
||||
this.throwErrorIfUserIdSameAuthorizedUser(userId, authorizedUser);
|
||||
const { systemUserRepository } = this.repositories;
|
||||
|
||||
@@ -112,8 +143,8 @@ export default class UsersService {
|
||||
|
||||
/**
|
||||
* Retrieve users list based on the given filter.
|
||||
* @param {number} tenantId
|
||||
* @param {object} filter
|
||||
* @param {number} tenantId
|
||||
* @param {object} filter
|
||||
*/
|
||||
async getList(tenantId: number) {
|
||||
const users = await SystemUser.query()
|
||||
@@ -134,7 +165,7 @@ export default class UsersService {
|
||||
|
||||
/**
|
||||
* Throws service error in case the user was already active.
|
||||
* @param {ISystemUser} user
|
||||
* @param {ISystemUser} user
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
throwErrorIfUserActive(user: ISystemUser) {
|
||||
@@ -145,7 +176,7 @@ export default class UsersService {
|
||||
|
||||
/**
|
||||
* Throws service error in case the user was already inactive.
|
||||
* @param {ISystemUser} user
|
||||
* @param {ISystemUser} user
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
throwErrorIfUserInactive(user: ISystemUser) {
|
||||
@@ -155,13 +186,16 @@ export default class UsersService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw service error in case the given user same the authorized user.
|
||||
* @param {number} userId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
* Throw service error in case the given user same the authorized user.
|
||||
* @param {number} userId
|
||||
* @param {ISystemUser} authorizedUser
|
||||
*/
|
||||
throwErrorIfUserIdSameAuthorizedUser(userId: number, authorizedUser: ISystemUser) {
|
||||
throwErrorIfUserIdSameAuthorizedUser(
|
||||
userId: number,
|
||||
authorizedUser: ISystemUser
|
||||
) {
|
||||
if (userId === authorizedUser.id) {
|
||||
throw new ServiceError('user_same_the_authorized_user');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import { Container } from 'typedi';
|
||||
import { EventSubscriber, On } from 'event-dispatch';
|
||||
import events from 'subscribers/events';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import InviteUserService from 'services/InviteUsers';
|
||||
|
||||
@EventSubscriber()
|
||||
export class InviteUserSubscriber {
|
||||
|
||||
@On(events.inviteUser.acceptInvite)
|
||||
public onAcceptInvite(payload) {
|
||||
const { inviteToken, user } = payload;
|
||||
const agenda = Container.get('agenda');
|
||||
|
||||
}
|
||||
|
||||
@On(events.inviteUser.checkInvite)
|
||||
public onCheckInvite(payload) {
|
||||
const { inviteToken, organizationOptions } = payload;
|
||||
const agenda = Container.get('agenda');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@On(events.inviteUser.sendInvite)
|
||||
public onSendInvite(payload) {
|
||||
const { invite } = payload;
|
||||
const { invite, authorizedUser, tenantId } = payload;
|
||||
const agenda = Container.get('agenda');
|
||||
|
||||
agenda.now('user-invite-mail', {
|
||||
invite, authorizedUser, tenantId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@ export default class SystemUser extends mixin(SystemModel, [SoftDelete({
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get virtualAttributes() {
|
||||
return ['fullName'];
|
||||
}
|
||||
|
||||
get fullName() {
|
||||
return (this.firstName + ' ' + this.lastName).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import SystemRepository from "system/repositories/SystemRepository";
|
||||
import { PlanSubscription } from 'system/models'
|
||||
import SystemRepository from 'system/repositories/SystemRepository';
|
||||
import { PlanSubscription } from 'system/models';
|
||||
|
||||
@Service()
|
||||
export default class SubscriptionRepository extends SystemRepository{
|
||||
@Inject('cache')
|
||||
cache: any;
|
||||
export default class SubscriptionRepository extends SystemRepository {
|
||||
/**
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
get model() {
|
||||
return PlanSubscription.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve subscription from a given slug in specific tenant.
|
||||
* @param {string} slug
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
getBySlugInTenant(slug: string, tenantId: number) {
|
||||
const key = `subscription.slug.${slug}.tenant.${tenantId}`;
|
||||
/**
|
||||
* Retrieve subscription from a given slug in specific tenant.
|
||||
* @param {string} slug
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
getBySlugInTenant(slug: string, tenantId: number) {
|
||||
const cacheKey = this.getCacheKey('getBySlugInTenant', slug, tenantId);
|
||||
|
||||
return this.cache.get(key, () => {
|
||||
return PlanSubscription.query().findOne('slug', slug).where('tenant_id', tenantId);
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return PlanSubscription.query()
|
||||
.findOne('slug', slug)
|
||||
.where('tenant_id', tenantId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import CachableRepository from "repositories/CachableRepository";
|
||||
|
||||
export default class SystemRepository extends CachableRepository {
|
||||
|
||||
export default class SystemRepository {
|
||||
|
||||
}
|
||||
@@ -1,24 +1,14 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import SystemRepository from "system/repositories/SystemRepository";
|
||||
import { SystemUser } from "system/models";
|
||||
import SystemRepository from 'system/repositories/SystemRepository';
|
||||
import { SystemUser } from 'system/models';
|
||||
import { ISystemUser } from 'interfaces';
|
||||
|
||||
@Service()
|
||||
export default class SystemUserRepository extends SystemRepository {
|
||||
@Inject('cache')
|
||||
cache: any;
|
||||
|
||||
/**
|
||||
* Patches the last login date to the given system user.
|
||||
* @param {number} userId
|
||||
* @return {Promise<void>}
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
async patchLastLoginAt(userId: number): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, {
|
||||
last_login_at: moment().toMySqlDateTime()
|
||||
});
|
||||
this.flushCache();
|
||||
get model() {
|
||||
return SystemUser.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,43 +18,42 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
findByCrediential(crediential: string): Promise<ISystemUser> {
|
||||
return SystemUser.query().whereNotDeleted()
|
||||
.findOne('email', crediential)
|
||||
.orWhere('phone_number', crediential);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve system user details of the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getById(userId: number): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.id.${userId}`, () => {
|
||||
return SystemUser.query().whereNotDeleted().findById(userId);
|
||||
const cacheKey = this.getCacheKey('findByCrediential', crediential);
|
||||
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return this.model.query()
|
||||
.whereNotDeleted()
|
||||
.findOne('email', crediential)
|
||||
.orWhere('phone_number', crediential);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve user by id and tenant id.
|
||||
* @param {number} userId - User id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} userId - User id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getByIdAndTenant(userId: number, tenantId: number): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.id.${userId}.tenant.${tenantId}`, () => {
|
||||
return SystemUser.query().whereNotDeleted()
|
||||
findOneByIdAndTenant(userId: number, tenantId: number): Promise<ISystemUser> {
|
||||
const cacheKey = this.getCacheKey('findOneByIdAndTenant', userId, tenantId);
|
||||
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return this.model.query()
|
||||
.whereNotDeleted()
|
||||
.findOne({ id: userId, tenant_id: tenantId });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve system user details by the given email.
|
||||
* @param {string} email - Email
|
||||
* @param {string} email - Email
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getByEmail(email: string): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.email.${email}`, () => {
|
||||
return SystemUser.query().whereNotDeleted().findOne('email', email);
|
||||
findOneByEmail(email: string): Promise<ISystemUser> {
|
||||
const cacheKey = this.getCacheKey('findOneByEmail', email);
|
||||
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return this.model.query().whereNotDeleted().findOne('email', email);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,69 +62,43 @@ export default class SystemUserRepository extends SystemRepository {
|
||||
* @param {string} phoneNumber - Phone number
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
getByPhoneNumber(phoneNumber: string): Promise<ISystemUser> {
|
||||
return this.cache.get(`systemUser.phoneNumber.${phoneNumber}`, () => {
|
||||
return SystemUser.query().whereNotDeleted().findOne('phoneNumber', phoneNumber);
|
||||
findOneByPhoneNumber(phoneNumber: string): Promise<ISystemUser> {
|
||||
const cacheKey = this.getCacheKey('findOneByPhoneNumber', phoneNumber);
|
||||
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return this.model.query()
|
||||
.whereNotDeleted()
|
||||
.findOne('phoneNumber', phoneNumber);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits details.
|
||||
* @param {number} userId - User id.
|
||||
* @param {number} user - User input.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async edit(userId: number, userInput: ISystemUser): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, { ...userInput });
|
||||
this.flushCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user.
|
||||
* @param {IUser} userInput - User input.
|
||||
* @return {Promise<ISystemUser>}
|
||||
*/
|
||||
async create(userInput: ISystemUser): Promise<ISystemUser> {
|
||||
const systemUser = await SystemUser.query().insert({ ...userInput });
|
||||
this.flushCache();
|
||||
|
||||
return systemUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* Patches the last login date to the given system user.
|
||||
* @param {number} userId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async deleteById(userId: number): Promise<void> {
|
||||
await SystemUser.query().where('id', userId).delete();
|
||||
this.flushCache();
|
||||
patchLastLoginAt(userId: number): Promise<void> {
|
||||
return super.update(
|
||||
{ last_login_at: moment().toMySqlDateTime() },
|
||||
{ id: userId }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async activateById(userId: number): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, { active: 1 });
|
||||
this.flushCache();
|
||||
activateById(userId: number): Promise<void> {
|
||||
return super.update({ active: 1 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate user by the given id.
|
||||
* @param {number} userId - User id.
|
||||
* @param {number} userId - User id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async inactivateById(userId: number): Promise<void> {
|
||||
await SystemUser.query().patchAndFetchById(userId, { active: 0 });
|
||||
this.flushCache();
|
||||
inactivateById(userId: number): Promise<void> {
|
||||
return super.update({ active: 0 }, { id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes user repository cache.
|
||||
*/
|
||||
flushCache() {
|
||||
this.cache.delStartWith('systemUser');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +1,43 @@
|
||||
import { Inject } from 'typedi';
|
||||
import moment from "moment";
|
||||
import { Tenant } from 'system/models';
|
||||
import SystemRepository from "./SystemRepository";
|
||||
import { ITenant } from 'interfaces';
|
||||
import uniqid from 'uniqid';
|
||||
import SystemRepository from "./SystemRepository";
|
||||
import { Tenant } from "system/models";
|
||||
import { ITenant } from 'interfaces';
|
||||
|
||||
export default class TenantRepository extends SystemRepository {
|
||||
@Inject('cache')
|
||||
cache: any;
|
||||
|
||||
/**
|
||||
* Flush the given tenant stored cache.
|
||||
* @param {ITenant} tenant
|
||||
* Gets the repository's model.
|
||||
*/
|
||||
flushTenantCache(tenant: ITenant) {
|
||||
this.cache.del(`tenant.org.${tenant.organizationId}`);
|
||||
this.cache.del(`tenant.id.${tenant.id}`);
|
||||
get model() {
|
||||
return Tenant.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tenant with random organization id.
|
||||
* @return {ITenant}
|
||||
*/
|
||||
newTenantWithUniqueOrgId(uniqId?: string): Promise<ITenant>{
|
||||
createWithUniqueOrgId(uniqId?: string): Promise<ITenant>{
|
||||
const organizationId = uniqid() || uniqId;
|
||||
return Tenant.query().insert({ organizationId });
|
||||
return super.create({ organizationId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark as seeded.
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
async markAsSeeded(tenantId: number) {
|
||||
const tenant = await Tenant.query()
|
||||
.patchAndFetchById(tenantId, {
|
||||
seeded_at: moment().toMySqlDateTime(),
|
||||
});
|
||||
this.flushTenantCache(tenant);
|
||||
markAsSeeded(tenantId: number) {
|
||||
return super.update({
|
||||
seededAt: moment().toMySqlDateTime(),
|
||||
}, { id: tenantId })
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the the given organization as initialized.
|
||||
* @param {string} organizationId
|
||||
*/
|
||||
async markAsInitialized(tenantId: number) {
|
||||
const tenant = await Tenant.query()
|
||||
.patchAndFetchById(tenantId, {
|
||||
initialized_at: moment().toMySqlDateTime(),
|
||||
});
|
||||
this.flushTenantCache(tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve tenant details by the given organization id.
|
||||
* @param {string} organizationId
|
||||
*/
|
||||
getByOrgId(organizationId: string) {
|
||||
return this.cache.get(`tenant.org.${organizationId}`, () => {
|
||||
return Tenant.query().findOne('organization_id', organizationId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve tenant details by the given tenant id.
|
||||
* @param {string} tenantId - Tenant id.
|
||||
*/
|
||||
getById(tenantId: number) {
|
||||
return this.cache.get(`tenant.id.${tenantId}`, () => {
|
||||
return Tenant.query().findById(tenantId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve tenant details with associated subscriptions
|
||||
* and plans by the given tenant id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
*/
|
||||
getByIdWithSubscriptions(tenantId: number) {
|
||||
return Tenant.query().findById(tenantId)
|
||||
.withGraphFetched('subscriptions.plan');
|
||||
markAsInitialized(tenantId: number) {
|
||||
return super.update({
|
||||
initializedAt: moment().toMySqlDateTime(),
|
||||
}, { id: tenantId });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user