From 17a38eafd12949b949672ec3a6c756b0b4f6c6cd Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 16 Dec 2020 15:37:12 +0200 Subject: [PATCH 1/4] feat: validate country code and phone number in user registration. --- server/package.json | 2 + server/src/api/controllers/Authentication.ts | 84 ++++++++++++++++++-- server/src/config/index.js | 12 +++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/server/package.json b/server/package.json index d8398fac5..da28bd13f 100644 --- a/server/package.json +++ b/server/package.json @@ -27,6 +27,7 @@ "bookshelf-modelbase": "^2.10.4", "bookshelf-paranoia": "^0.13.1", "compression": "^1.7.4", + "country-codes-list": "^1.6.8", "crypto-random-string": "^3.2.0", "csurf": "^1.10.0", "deep-map": "^2.0.0", @@ -48,6 +49,7 @@ "knex": "^0.20.3", "knex-cleaner": "^1.3.0", "knex-db-manager": "^0.6.1", + "libphonenumber-js": "^1.9.6", "lodash": "^4.17.15", "memory-cache": "^0.2.0", "moment": "^2.24.0", diff --git a/server/src/api/controllers/Authentication.ts b/server/src/api/controllers/Authentication.ts index 77e6a6b10..54160e6e5 100644 --- a/server/src/api/controllers/Authentication.ts +++ b/server/src/api/controllers/Authentication.ts @@ -1,6 +1,8 @@ import { Request, Response, Router } from 'express'; import { check, ValidationChain } from 'express-validator'; import { Service, Inject } from 'typedi'; +import countries from 'country-codes-list'; +import parsePhoneNumber from 'libphonenumber-js'; import BaseController from 'api/controllers/BaseController'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; import AuthenticationService from 'services/Authentication'; @@ -8,6 +10,7 @@ import { ILoginDTO, ISystemUser, IRegisterOTD } from 'interfaces'; import { ServiceError, ServiceErrors } from "exceptions"; import { DATATYPES_LENGTH } from 'data/DataTypes'; import LoginThrottlerMiddleware from 'api/middleware/LoginThrottlerMiddleware'; +import config from 'config'; @Service() export default class AuthenticationController extends BaseController{ @@ -63,15 +66,84 @@ export default class AuthenticationController extends BaseController{ */ get registerSchema(): ValidationChain[] { return [ - check('first_name').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('last_name').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('email').exists().isString().isEmail().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('phone_number').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('password').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('country').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('first_name') + .exists() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('last_name') + .exists() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('email') + .exists() + .isString() + .isEmail() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('phone_number') + .exists() + .isString() + .trim() + .escape() + .custom(this.phoneNumberValidator) + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('password') + .exists() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('country') + .exists() + .isString() + .trim() + .escape() + .custom(this.countryValidator) + .isLength({ max: DATATYPES_LENGTH.STRING }), ]; } + /** + * Country validator. + */ + countryValidator(value, { req }) { + const { countries: { whitelist, blacklist } } = config.registration; + const foundCountry = countries.findOne('countryCode', value); + + if (!foundCountry) { + throw new Error('The country code is invalid.'); + } + if ( + // Focus with me! In case whitelist is not empty and the given coutry is not + // in whitelist throw the error. + // + // And in case the blacklist is not empty and the given country exists + // in the blacklist throw the goddamn error. + (whitelist.length > 0 && whitelist.indexOf(value) === -1) && + (blacklist.length > 0 && blacklist.indexOf(value) !== -1) + ) { + throw new Error('The country code is not supported yet.'); + } + return true; + } + + /** + * Phone number validator. + */ + phoneNumberValidator(value, { req }) { + const phoneNumber = parsePhoneNumber(value, req.body.country); + + if (!phoneNumber || !phoneNumber.isValid()) { + throw new Error('Phone number is invalid with the given country code.'); + } + return true; + } + /** * Reset password schema. */ diff --git a/server/src/config/index.js b/server/src/config/index.js index a25409724..23f014317 100644 --- a/server/src/config/index.js +++ b/server/src/config/index.js @@ -144,5 +144,17 @@ export default { duration: 60, blockDuration: 60 * 10, } + }, + + /** + * Users registeration configuration. + */ + registration: { + countries: { + whitelist: [ + 'LY', + ], + blacklist: [], + } } }; \ No newline at end of file From b3eef8718d2438ced93c1b44e4ddbbfffb056153 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 16 Dec 2020 15:42:22 +0200 Subject: [PATCH 2/4] fix: validation country and phone number. --- server/src/api/controllers/Authentication.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/api/controllers/Authentication.ts b/server/src/api/controllers/Authentication.ts index 54160e6e5..a63880c5b 100644 --- a/server/src/api/controllers/Authentication.ts +++ b/server/src/api/controllers/Authentication.ts @@ -122,9 +122,9 @@ export default class AuthenticationController extends BaseController{ // Focus with me! In case whitelist is not empty and the given coutry is not // in whitelist throw the error. // - // And in case the blacklist is not empty and the given country exists + // Or in case the blacklist is not empty and the given country exists // in the blacklist throw the goddamn error. - (whitelist.length > 0 && whitelist.indexOf(value) === -1) && + (whitelist.length > 0 && whitelist.indexOf(value) === -1) || (blacklist.length > 0 && blacklist.indexOf(value) !== -1) ) { throw new Error('The country code is not supported yet.'); From 42c2e583ed92a96ce68ab57b5038658b75db4e4c Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 16 Dec 2020 16:23:00 +0200 Subject: [PATCH 3/4] feat: ability to activate and inactivate the given item. --- server/src/api/controllers/Items.ts | 60 +++++++++++++++++++++++ server/src/services/Items/ItemsService.ts | 34 +++++++++++++ 2 files changed, 94 insertions(+) diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index b0f83caec..004d16fc6 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -30,6 +30,22 @@ export default class ItemsController extends BaseController { asyncMiddleware(this.newItem.bind(this)), this.handlerServiceErrors, ); + router.post( + '/:id/activate', [ + ...this.validateSpecificItemSchema, + ], + this.validationResult, + asyncMiddleware(this.activateItem.bind(this)), + this.handlerServiceErrors + ); + router.post( + '/:id/inactivate', [ + ...this.validateSpecificItemSchema, + ], + this.validationResult, + asyncMiddleware(this.inactivateItem.bind(this)), + this.handlerServiceErrors, + ) router.post( '/:id', [ ...this.validateItemSchema, @@ -226,6 +242,50 @@ export default class ItemsController extends BaseController { } } + /** + * Activates the given item. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + async activateItem(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + const itemId: number = req.params.id; + + try { + await this.itemsService.activateItem(tenantId, itemId); + + return res.status(200).send({ + id: itemId, + message: 'The item has been activated successfully.', + }); + } catch (error) { + next(error); + } + } + + /** + * Inactivates the given item. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + async inactivateItem(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + const itemId: number = req.params.id; + + try { + await this.itemsService.inactivateItem(tenantId, itemId); + + return res.status(200).send({ + id: itemId, + message: 'The item has been inactivated successfully.', + }); + } catch (error) { + next(error); + } + } + /** * Deletes the given item from the storage. * @param {Request} req diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 4e8394579..430aa4e2a 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -232,6 +232,40 @@ export default class ItemsService implements IItemsService { await Item.query().findById(itemId).delete(); this.logger.info('[items] deleted successfully.', { tenantId, itemId }); } + + /** + * Activates the given item on the storage. + * @param {number} tenantId - + * @param {number} itemId - + * @return {Promise} + */ + public async activateItem(tenantId: number, itemId: number): Promise { + const { Item } = this.tenancy.models(tenantId); + + this.logger.info('[items] trying to activate the given item.', { tenantId, itemId }); + const item = await this.getItemOrThrowError(tenantId, itemId); + + await Item.query().findById(itemId).patch({ active: true }); + + this.logger.info('[items] activated successfully.', { tenantId, itemId }); + } + + /** + * Inactivates the given item on the storage. + * @param {number} tenantId + * @param {number} itemId + * @return {Promise} + */ + public async inactivateItem(tenantId: number, itemId: number): Promise { + const { Item } = this.tenancy.models(tenantId); + + this.logger.info('[items] trying to inactivate the given item.', { tenantId, itemId }); + const item = await this.getItemOrThrowError(tenantId, itemId); + + await Item.query().findById(itemId).patch({ active: false }); + + this.logger.info('[items] activated successfully.', { tenantId, itemId }); + } /** * Retrieve the item details of the given id with associated details. From 3ac6d8897ec69b37a9f6b63dcf6e5fa5e3813adf Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 16 Dec 2020 16:38:18 +0200 Subject: [PATCH 4/4] fix: delete item from the storage. --- server/src/api/controllers/Items.ts | 10 +- server/src/services/Items/ItemsService.ts | 308 +++++++++++++++------- 2 files changed, 226 insertions(+), 92 deletions(-) diff --git a/server/src/api/controllers/Items.ts b/server/src/api/controllers/Items.ts index 004d16fc6..5faa275ef 100644 --- a/server/src/api/controllers/Items.ts +++ b/server/src/api/controllers/Items.ts @@ -186,7 +186,7 @@ export default class ItemsController extends BaseController { */ get validateBulkSelectSchema(): ValidationChain[] { return [ - query('ids').isArray({ min: 2 }), + query('ids').isArray({ min: 1 }), query('ids.*').isNumeric().toInt(), ]; } @@ -318,6 +318,8 @@ export default class ItemsController extends BaseController { return res.status(200).send({ item: storedItem }); } catch (error) { + console.log(error); + next(error) } } @@ -369,7 +371,11 @@ export default class ItemsController extends BaseController { try { await this.itemsService.bulkDeleteItems(tenantId, itemsIds); - return res.status(200).send({ ids: itemsIds }); + + return res.status(200).send({ + ids: itemsIds, + message: 'Items have been deleted successfully.', + }); } catch (error) { next(error); } diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 430aa4e2a..8b32fac9c 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -1,9 +1,9 @@ -import { defaultTo, difference } from "lodash"; -import { Service, Inject } from "typedi"; +import { defaultTo, difference } from 'lodash'; +import { Service, Inject } from 'typedi'; import { IItemsFilter, IItemsService, IItemDTO, IItem } from 'interfaces'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import TenancyService from 'services/Tenancy/TenancyService'; -import { ServiceError } from "exceptions"; +import { ServiceError } from 'exceptions'; const ERRORS = { NOT_FOUND: 'NOT_FOUND', @@ -18,7 +18,7 @@ const ERRORS = { INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY', ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS', - ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS' + ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', }; @Service() @@ -34,11 +34,14 @@ export default class ItemsService implements IItemsService { /** * Retrieve item details or throw not found error. - * @param {number} tenantId - * @param {number} itemId + * @param {number} tenantId + * @param {number} itemId * @return {Promise} */ - private async getItemOrThrowError(tenantId: number, itemId: number): Promise { + private async getItemOrThrowError( + tenantId: number, + itemId: number + ): Promise { const { Item } = this.tenancy.models(tenantId); this.logger.info('[items] validate item id existance.', { itemId }); @@ -53,15 +56,22 @@ export default class ItemsService implements IItemsService { /** * Validate wether the given item name already exists on the storage. - * @param {number} tenantId - * @param {string} itemName - * @param {number} notItemId + * @param {number} tenantId + * @param {string} itemName + * @param {number} notItemId * @return {Promise} */ - private async validateItemNameUniquiness(tenantId: number, itemName: string, notItemId?: number): Promise { + private async validateItemNameUniquiness( + tenantId: number, + itemName: string, + notItemId?: number + ): Promise { const { Item } = this.tenancy.models(tenantId); - this.logger.info('[items] validate item name uniquiness.', { itemName, tenantId }); + this.logger.info('[items] validate item name uniquiness.', { + itemName, + tenantId, + }); const foundItems: [] = await Item.query().onBuild((builder: any) => { builder.where('name', itemName); if (notItemId) { @@ -69,29 +79,47 @@ export default class ItemsService implements IItemsService { } }); if (foundItems.length > 0) { - this.logger.info('[items] item name already exists.', { itemName, tenantId }); + this.logger.info('[items] item name already exists.', { + itemName, + tenantId, + }); throw new ServiceError(ERRORS.ITEM_NAME_EXISTS); } } /** * Validate item COGS account existance and type. - * @param {number} tenantId - * @param {number} costAccountId + * @param {number} tenantId + * @param {number} costAccountId * @return {Promise} */ - private async validateItemCostAccountExistance(tenantId: number, costAccountId: number): Promise { - const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId); + private async validateItemCostAccountExistance( + tenantId: number, + costAccountId: number + ): Promise { + const { + accountRepository, + accountTypeRepository, + } = this.tenancy.repositories(tenantId); - this.logger.info('[items] validate cost account existance.', { tenantId, costAccountId }); + this.logger.info('[items] validate cost account existance.', { + tenantId, + costAccountId, + }); const COGSType = await accountTypeRepository.getByKey('cost_of_goods_sold'); - const foundAccount = await accountRepository.findOneById(costAccountId) + const foundAccount = await accountRepository.findOneById(costAccountId); if (!foundAccount) { - this.logger.info('[items] cost account not found.', { tenantId, costAccountId }); + this.logger.info('[items] cost account not found.', { + tenantId, + costAccountId, + }); throw new ServiceError(ERRORS.COST_ACCOUNT_NOT_FOUMD); } else if (foundAccount.accountTypeId !== COGSType.id) { - this.logger.info('[items] validate cost account not COGS type.', { tenantId, costAccountId }); + this.logger.info('[items] validate cost account not COGS type.', { + tenantId, + costAccountId, + }); throw new ServiceError(ERRORS.COST_ACCOUNT_NOT_COGS); } } @@ -101,49 +129,84 @@ export default class ItemsService implements IItemsService { * @param {number} tenantId - Tenant id. * @param {number} sellAccountId - Sell account id. */ - private async validateItemSellAccountExistance(tenantId: number, sellAccountId: number) { - const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId); + private async validateItemSellAccountExistance( + tenantId: number, + sellAccountId: number + ) { + const { + accountRepository, + accountTypeRepository, + } = this.tenancy.repositories(tenantId); - this.logger.info('[items] validate sell account existance.', { tenantId, sellAccountId }); + this.logger.info('[items] validate sell account existance.', { + tenantId, + sellAccountId, + }); const incomeType = await accountTypeRepository.getByKey('income'); const foundAccount = await accountRepository.findOneById(sellAccountId); if (!foundAccount) { - this.logger.info('[items] sell account not found.', { tenantId, sellAccountId }); - throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_FOUND) + this.logger.info('[items] sell account not found.', { + tenantId, + sellAccountId, + }); + throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_FOUND); } else if (foundAccount.accountTypeId !== incomeType.id) { - this.logger.info('[items] sell account not income type.', { tenantId, sellAccountId }); + this.logger.info('[items] sell account not income type.', { + tenantId, + sellAccountId, + }); throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_INCOME); } } /** * Validate item inventory account existance and type. - * @param {number} tenantId - * @param {number} inventoryAccountId + * @param {number} tenantId + * @param {number} inventoryAccountId */ - private async validateItemInventoryAccountExistance(tenantId: number, inventoryAccountId: number) { - const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId); + private async validateItemInventoryAccountExistance( + tenantId: number, + inventoryAccountId: number + ) { + const { + accountTypeRepository, + accountRepository, + } = this.tenancy.repositories(tenantId); - this.logger.info('[items] validate inventory account existance.', { tenantId, inventoryAccountId }); + this.logger.info('[items] validate inventory account existance.', { + tenantId, + inventoryAccountId, + }); const otherAsset = await accountTypeRepository.getByKey('other_asset'); - const foundAccount = await accountRepository.findOneById(inventoryAccountId); + const foundAccount = await accountRepository.findOneById( + inventoryAccountId + ); if (!foundAccount) { - this.logger.info('[items] inventory account not found.', { tenantId, inventoryAccountId }); - throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_FOUND) + this.logger.info('[items] inventory account not found.', { + tenantId, + inventoryAccountId, + }); + throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_FOUND); } else if (otherAsset.id !== foundAccount.accountTypeId) { - this.logger.info('[items] inventory account not inventory type.', { tenantId, inventoryAccountId }); + this.logger.info('[items] inventory account not inventory type.', { + tenantId, + inventoryAccountId, + }); throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_INVENTORY); } } /** * Validate item category existance. - * @param {number} tenantId - * @param {number} itemCategoryId + * @param {number} tenantId + * @param {number} itemCategoryId */ - private async validateItemCategoryExistance(tenantId: number, itemCategoryId: number) { + private async validateItemCategoryExistance( + tenantId: number, + itemCategoryId: number + ) { const { ItemCategory } = this.tenancy.models(tenantId); const foundCategory = await ItemCategory.query().findById(itemCategoryId); @@ -155,7 +218,7 @@ export default class ItemsService implements IItemsService { /** * Creates a new item. * @param {number} tenantId DTO - * @param {IItemDTO} item + * @param {IItemDTO} item * @return {Promise} */ public async newItem(tenantId: number, itemDTO: IItemDTO): Promise { @@ -168,28 +231,40 @@ export default class ItemsService implements IItemsService { await this.validateItemCategoryExistance(tenantId, itemDTO.categoryId); } if (itemDTO.sellAccountId) { - await this.validateItemSellAccountExistance(tenantId, itemDTO.sellAccountId); + await this.validateItemSellAccountExistance( + tenantId, + itemDTO.sellAccountId + ); } if (itemDTO.costAccountId) { - await this.validateItemCostAccountExistance(tenantId, itemDTO.costAccountId); + await this.validateItemCostAccountExistance( + tenantId, + itemDTO.costAccountId + ); } if (itemDTO.inventoryAccountId) { - await this.validateItemInventoryAccountExistance(tenantId, itemDTO.inventoryAccountId); + await this.validateItemInventoryAccountExistance( + tenantId, + itemDTO.inventoryAccountId + ); } const storedItem = await Item.query().insertAndFetch({ ...itemDTO, active: defaultTo(itemDTO.active, 1), }); - this.logger.info('[items] item inserted successfully.', { tenantId, itemDTO }); + this.logger.info('[items] item inserted successfully.', { + tenantId, + itemDTO, + }); return storedItem; } /** * Edits the item metadata. - * @param {number} tenantId - * @param {number} itemId - * @param {IItemDTO} itemDTO + * @param {number} tenantId + * @param {number} itemId + * @param {IItemDTO} itemDTO */ public async editItem(tenantId: number, itemId: number, itemDTO: IItemDTO) { const { Item } = this.tenancy.models(tenantId); @@ -197,21 +272,40 @@ export default class ItemsService implements IItemsService { // Validates the given item existance on the storage. const oldItem = await this.getItemOrThrowError(tenantId, itemId); + // Validate the item category existance on the storage, if (itemDTO.categoryId) { await this.validateItemCategoryExistance(tenantId, itemDTO.categoryId); } + // Validate the sell account existance on the storage. if (itemDTO.sellAccountId) { - await this.validateItemSellAccountExistance(tenantId, itemDTO.sellAccountId); + await this.validateItemSellAccountExistance( + tenantId, + itemDTO.sellAccountId + ); } + // Validate the cost account existance on the storage. if (itemDTO.costAccountId) { - await this.validateItemCostAccountExistance(tenantId, itemDTO.costAccountId); + await this.validateItemCostAccountExistance( + tenantId, + itemDTO.costAccountId + ); } + // Validate the inventory account existance onthe storage. if (itemDTO.inventoryAccountId) { - await this.validateItemInventoryAccountExistance(tenantId, itemDTO.inventoryAccountId); + await this.validateItemInventoryAccountExistance( + tenantId, + itemDTO.inventoryAccountId + ); } - const newItem = await Item.query().patchAndFetchById(itemId, { ...itemDTO }); - this.logger.info('[items] item edited successfully.', { tenantId, itemId, itemDTO }); + const newItem = await Item.query().patchAndFetchById(itemId, { + ...itemDTO, + }); + this.logger.info('[items] item edited successfully.', { + tenantId, + itemId, + itemDTO, + }); return newItem; } @@ -228,11 +322,11 @@ export default class ItemsService implements IItemsService { this.logger.info('[items] trying to delete item.', { tenantId, itemId }); await this.getItemOrThrowError(tenantId, itemId); await this.validateHasNoInvoicesOrBills(tenantId, itemId); - + await Item.query().findById(itemId).delete(); this.logger.info('[items] deleted successfully.', { tenantId, itemId }); } - + /** * Activates the given item on the storage. * @param {number} tenantId - @@ -242,7 +336,10 @@ export default class ItemsService implements IItemsService { public async activateItem(tenantId: number, itemId: number): Promise { const { Item } = this.tenancy.models(tenantId); - this.logger.info('[items] trying to activate the given item.', { tenantId, itemId }); + this.logger.info('[items] trying to activate the given item.', { + tenantId, + itemId, + }); const item = await this.getItemOrThrowError(tenantId, itemId); await Item.query().findById(itemId).patch({ active: true }); @@ -252,14 +349,17 @@ export default class ItemsService implements IItemsService { /** * Inactivates the given item on the storage. - * @param {number} tenantId - * @param {number} itemId + * @param {number} tenantId + * @param {number} itemId * @return {Promise} */ - public async inactivateItem(tenantId: number, itemId: number): Promise { + public async inactivateItem(tenantId: number, itemId: number): Promise { const { Item } = this.tenancy.models(tenantId); - this.logger.info('[items] trying to inactivate the given item.', { tenantId, itemId }); + this.logger.info('[items] trying to inactivate the given item.', { + tenantId, + itemId, + }); const item = await this.getItemOrThrowError(tenantId, itemId); await Item.query().findById(itemId).patch({ active: false }); @@ -269,15 +369,26 @@ export default class ItemsService implements IItemsService { /** * Retrieve the item details of the given id with associated details. - * @param {number} tenantId - * @param {number} itemId + * @param {number} tenantId + * @param {number} itemId */ public async getItem(tenantId: number, itemId: number): Promise { const { Item } = this.tenancy.models(tenantId); - const item = Item.query().findById(itemId) - .withGraphFetched('costAccount', 'sellAccount', 'inventoryAccount', 'category'); - + this.logger.info('[items] trying to get the specific item.', { + tenantId, + itemId, + }); + + const item = await Item.query() + .findById(itemId) + .withGraphFetched( + 'costAccount', + 'sellAccount', + 'inventoryAccount', + 'category' + ); + if (!item) { throw new ServiceError(ERRORS.NOT_FOUND); } @@ -286,7 +397,7 @@ export default class ItemsService implements IItemsService { /** * Validates the given items IDs exists or not returns the not found ones. - * @param {Array} itemsIDs + * @param {Array} itemsIDs * @return {Array} */ private async validateItemsIdsExists(tenantId: number, itemsIDs: number[]) { @@ -301,41 +412,54 @@ export default class ItemsService implements IItemsService { /** * Deletes items in bulk. - * @param {number} tenantId - * @param {number[]} itemsIds + * @param {number} tenantId + * @param {number[]} itemsIds */ public async bulkDeleteItems(tenantId: number, itemsIds: number[]) { const { Item } = this.tenancy.models(tenantId); - this.logger.info('[items] trying to delete items in bulk.', { tenantId, itemsIds }); + this.logger.info('[items] trying to delete items in bulk.', { + tenantId, + itemsIds, + }); await this.validateItemsIdsExists(tenantId, itemsIds); await this.validateHasNoInvoicesOrBills(tenantId, itemsIds); await Item.query().whereIn('id', itemsIds).delete(); - this.logger.info('[items] deleted successfully in bulk.', { tenantId, itemsIds }); + this.logger.info('[items] deleted successfully in bulk.', { + tenantId, + itemsIds, + }); } - + /** * Retrieve items datatable list. - * @param {number} tenantId - * @param {IItemsFilter} itemsFilter + * @param {number} tenantId + * @param {IItemsFilter} itemsFilter */ public async itemsList(tenantId: number, itemsFilter: IItemsFilter) { const { Item } = this.tenancy.models(tenantId); - const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, Item, itemsFilter); - - const { results, pagination } = await Item.query().onBuild((builder) => { - builder.withGraphFetched('inventoryAccount'); - builder.withGraphFetched('sellAccount'); - builder.withGraphFetched('costAccount'); - builder.withGraphFetched('category'); - - dynamicFilter.buildQuery()(builder); - }).pagination( - itemsFilter.page - 1, - itemsFilter.pageSize, + const dynamicFilter = await this.dynamicListService.dynamicList( + tenantId, + Item, + itemsFilter ); - return { items: results, pagination, filterMeta: dynamicFilter.getResponseMeta() }; + + const { results, pagination } = await Item.query() + .onBuild((builder) => { + builder.withGraphFetched('inventoryAccount'); + builder.withGraphFetched('sellAccount'); + builder.withGraphFetched('costAccount'); + builder.withGraphFetched('category'); + + dynamicFilter.buildQuery()(builder); + }) + .pagination(itemsFilter.page - 1, itemsFilter.pageSize); + return { + items: results, + pagination, + filterMeta: dynamicFilter.getResponseMeta(), + }; } /** @@ -344,7 +468,10 @@ export default class ItemsService implements IItemsService { * @param {number|number[]} itemId - Item id. * @throws {ServiceError} */ - private async validateHasNoInvoicesOrBills(tenantId: number, itemId: number[]|number) { + private async validateHasNoInvoicesOrBills( + tenantId: number, + itemId: number[] | number + ) { const { ItemEntry } = this.tenancy.models(tenantId); const ids = Array.isArray(itemId) ? itemId : [itemId]; @@ -353,10 +480,11 @@ export default class ItemsService implements IItemsService { .whereIn('reference_type', ['SaleInvoice', 'Bill']); if (foundItemEntries.length > 0) { - throw new ServiceError(ids.length > 1 ? - ERRORS.ITEMS_HAVE_ASSOCIATED_TRANSACTIONS : - ERRORS.ITEM_HAS_ASSOCIATED_TRANSACTINS + throw new ServiceError( + ids.length > 1 + ? ERRORS.ITEMS_HAVE_ASSOCIATED_TRANSACTIONS + : ERRORS.ITEM_HAS_ASSOCIATED_TRANSACTINS ); } } -} \ No newline at end of file +}