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