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