fix: system repositories.

This commit is contained in:
a.bouhuolia
2020-12-17 17:19:16 +02:00
parent 7a847fc895
commit a67b1fbdd0
54 changed files with 1452 additions and 983 deletions

View File

@@ -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);
} }
}; }

View File

@@ -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);
} }
}; }

View File

@@ -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 }],
}); });

View File

@@ -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());

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -160,5 +160,8 @@ export default {
], ],
blacklist: [], blacklist: [],
} }
} },
protocol: '',
hostname: '',
}; };

View File

@@ -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);
} }
} }

View File

@@ -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';

View File

@@ -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');

View File

@@ -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),
}; };
} }

View File

@@ -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);

View File

@@ -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';

View File

@@ -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);

View File

@@ -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;
}
} }

View File

@@ -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) {

View File

@@ -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;
} }
/** /**

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
/** /**

View File

@@ -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;
} }
} }

View File

@@ -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) {

View File

@@ -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.");
} }
/** /**

View File

@@ -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;
} }
} }

View File

@@ -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(),
}); });

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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) {

View File

@@ -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;
} }
/** /**

View File

@@ -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);

View File

@@ -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;

View File

@@ -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');

View File

@@ -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,
}
);
} }
} }

View File

@@ -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,

View File

@@ -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,
}
);
} }
} }

View File

@@ -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,
});
} }
/** /**

View File

@@ -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.');
} }
} }

View File

@@ -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
); );

View File

@@ -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.

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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');
} }

View File

@@ -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
});
} }
} }

View File

@@ -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.
*/ */

View File

@@ -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);
});
}
} }

View File

@@ -1,5 +1,5 @@
import CachableRepository from "repositories/CachableRepository";
export default class SystemRepository extends CachableRepository {
export default class SystemRepository {
} }

View File

@@ -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');
} }
} }

View File

@@ -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');
} }
} }