diff --git a/server/src/api/controllers/ItemCategories.ts b/server/src/api/controllers/ItemCategories.ts index 90245f6a0..0d54a3c0c 100644 --- a/server/src/api/controllers/ItemCategories.ts +++ b/server/src/api/controllers/ItemCategories.ts @@ -1,18 +1,22 @@ -import { Router, Request, Response } from 'express'; +import { Router, Request, Response, NextFunction } from 'express'; import { check, param, query, } from 'express-validator'; -import { difference } from 'lodash'; -import { Service } from 'typedi'; +import ItemCategoriesService from 'services/ItemCategories/ItemCategoriesService'; +import { Inject, Service } from 'typedi'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; import validateMiddleware from 'api/middleware/validateMiddleware'; -import { IItemCategory, IItemCategoryOTD } from 'interfaces'; +import { IItemCategoryOTD } from 'interfaces'; +import { ServiceError } from 'exceptions'; import BaseController from 'api/controllers/BaseController'; @Service() export default class ItemsCategoriesController extends BaseController { + @Inject() + itemCategoriesService: ItemCategoriesService; + /** * Router constructor method. */ @@ -24,43 +28,43 @@ export default class ItemsCategoriesController extends BaseController { ...this.specificCategoryValidationSchema, ], validateMiddleware, - asyncMiddleware(this.validateParentCategoryExistance), - asyncMiddleware(this.validateSellAccountExistance), - asyncMiddleware(this.validateCostAccountExistance), - asyncMiddleware(this.validateInventoryAccountExistance), - asyncMiddleware(this.editCategory) + asyncMiddleware(this.editCategory.bind(this)), + this.handlerServiceError, ); - router.post('/', - this.categoryValidationSchema, + router.post('/', [ + ...this.categoryValidationSchema, + ], validateMiddleware, - asyncMiddleware(this.validateParentCategoryExistance), - asyncMiddleware(this.validateSellAccountExistance), - asyncMiddleware(this.validateCostAccountExistance), - asyncMiddleware(this.validateInventoryAccountExistance), - asyncMiddleware(this.newCategory), + asyncMiddleware(this.newCategory.bind(this)), + this.handlerServiceError, ); - router.delete('/bulk', - this.categoriesBulkValidationSchema, + router.delete('/', [ + ...this.categoriesBulkValidationSchema, + ], validateMiddleware, - asyncMiddleware(this.validateCategoriesIdsExistance), - asyncMiddleware(this.bulkDeleteCategories), + asyncMiddleware(this.bulkDeleteCategories.bind(this)), + this.handlerServiceError, ); - router.delete('/:id', - this.specificCategoryValidationSchema, + router.delete('/:id', [ + ...this.specificCategoryValidationSchema + ], validateMiddleware, - asyncMiddleware(this.validateItemCategoryExistance), - asyncMiddleware(this.deleteItem), + asyncMiddleware(this.deleteItem.bind(this)), + this.handlerServiceError, ); - router.get('/:id', - this.specificCategoryValidationSchema, + router.get('/:id', [ + ...this.specificCategoryValidationSchema, + ], validateMiddleware, - asyncMiddleware(this.validateItemCategoryExistance), - asyncMiddleware(this.getCategory) + asyncMiddleware(this.getCategory.bind(this)), + this.handlerServiceError, ); - router.get('/', - this.categoriesListValidationSchema, + router.get('/', [ + ...this.categoriesListValidationSchema + ], validateMiddleware, - asyncMiddleware(this.getList) + asyncMiddleware(this.getList.bind(this)), + this.handlerServiceError, ); return router; } @@ -122,242 +126,168 @@ export default class ItemsCategoriesController extends BaseController { return [ param('id').exists().toInt(), ]; - } - - /** - * Validate the item category existance. - * @param {Request} req - * @param {Response} res - */ - async validateItemCategoryExistance(req: Request, res: Response, next: Function) { - const categoryId: number = req.params.id; - const { ItemCategory } = req.models; - const category = await ItemCategory.query().findById(categoryId); - - if (!category) { - return res.boom.notFound(null, { - errors: [{ type: 'ITEM_CATEGORY_NOT_FOUND', code: 100 }], - }); - } - next(); - } - - /** - * Validate wether the given cost account exists on the storage. - * @param {Request} req - * @param {Response} res - * @param {Function} next - */ - async validateCostAccountExistance(req: Request, res: Response, next: Function) { - const { Account, AccountType } = req.models; - const category: IItemCategoryOTD = this.matchedBodyData(req); - - if (category.costAccountId) { - const COGSType = await AccountType.query().findOne('key', 'cost_of_goods_sold'); - const foundAccount = await Account.query().findById(category.costAccountId) - - if (!foundAccount) { - return res.status(400).send({ - errors: [{ type: 'COST.ACCOUNT.NOT.FOUND', code: 120 }], - }); - } else if (foundAccount.accountTypeId !== COGSType.id) { - return res.status(400).send({ - errors: [{ type: 'COST.ACCOUNT.NOT.COGS.TYPE', code: 220 }], - }); - } - } - next(); - } - - /** - * Validate wether the given sell account exists on the storage. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async validateSellAccountExistance(req: Request, res: Response, next: Function) { - const { Account, AccountType } = req.models; - const category: IItemCategoryOTD = this.matchedBodyData(req); - - if (category.sellAccountId) { - const incomeType = await AccountType.query().findOne('key', 'income'); - const foundAccount = await Account.query().findById(category.sellAccountId); - - if (!foundAccount) { - return res.status(400).send({ - errors: [{ type: 'SELL.ACCOUNT.NOT.FOUND', code: 130 }], - }); - } else if (foundAccount.accountTypeId !== incomeType.id) { - return res.status(400).send({ - errors: [{ type: 'SELL.ACCOUNT.NOT.INCOME.TYPE', code: 230 }], - }) - } - } - next(); - } - - /** - * Validates wether the given inventory account exists on the storage. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async validateInventoryAccountExistance(req: Request, res: Response, next: Function) { - const { Account, AccountType } = req.models; - const category: IItemCategoryOTD = this.matchedBodyData(req); - - if (category.inventoryAccountId) { - const otherAsset = await AccountType.query().findOne('key', 'other_asset'); - const foundAccount = await Account.query().findById(category.inventoryAccountId); - - if (!foundAccount) { - return res.status(400).send({ - errors: [{ type: 'INVENTORY.ACCOUNT.NOT.FOUND', code: 200}], - }); - } else if (otherAsset.id !== foundAccount.accountTypeId) { - return res.status(400).send({ - errors: [{ type: 'INVENTORY.ACCOUNT.NOT.CURRENT.ASSET', code: 300 }], - }); - } - } - next(); - } - - /** - * Validate the item category parent category whether exists on the storage. - * @param {Request} req - * @param {Response} res - * @param {Function} next - */ - async validateParentCategoryExistance(req: Request, res: Response, next: Function) { - const category: IItemCategory = this.matchedBodyData(req); - const { ItemCategory } = req.models; - - if (category.parentCategoryId) { - const foundParentCategory = await ItemCategory.query() - .where('id', category.parentCategoryId) - .first(); - - if (!foundParentCategory) { - return res.boom.notFound('The parent category ID is not found.', { - errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }], - }); - } - } - next(); - } - - /** - * Validate item categories ids existance. - * @param {Request} req - * @param {Response} res - * @param {Function} next - */ - async validateCategoriesIdsExistance(req: Request, res: Response, next: Function) { - const ids: number[] = (req.query?.ids || []); - const { ItemCategory } = req.models; - - const itemCategories = await ItemCategory.query().whereIn('id', ids); - const itemCategoriesIds = itemCategories.map((category: IItemCategory) => category.id); - const notFoundCategories = difference(ids, itemCategoriesIds); - - if (notFoundCategories.length > 0) { - return res.status(400).send({ - errors: [{ type: 'ITEM.CATEGORIES.IDS.NOT.FOUND', code: 200 }], - }); - } - next(); - } + } /** * Creates a new item category. * @param {Request} req * @param {Response} res */ - async newCategory(req: Request, res: Response) { - const { user } = req; - const category: IItemCategory = this.matchedBodyData(req); - const { ItemCategory } = req.models; + async newCategory(req: Request, res: Response, next: NextFunction) { + const { user, tenantId } = req; + const itemCategoryOTD: IItemCategoryOTD = this.matchedBodyData(req); - const storedCategory = await ItemCategory.query().insert({ - ...category, - user_id: user.id, - }); - return res.status(200).send({ category: storedCategory }); + try { + const itemCategory = await this.itemCategoriesService.newItemCategory(tenantId, itemCategoryOTD, user); + return res.status(200).send({ id: itemCategory.id }); + } catch (error) { + next(error); + } } /** * 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) { - const { id } = req.params; - const category: IItemCategory = this.matchedBodyData(req); - const { ItemCategory } = req.models; + async editCategory(req: Request, res: Response, next: NextFunction) { + const { tenantId, user } = req; + const { id: itemCategoryId } = req.params; + const itemCategoryOTD: IItemCategoryOTD = this.matchedBodyData(req); - const updateItemCategory = await ItemCategory.query() - .where('id', id) - .update({ ...category }); - - return res.status(200).send({ id }); + try { + await this.itemCategoriesService.editItemCategory(tenantId, itemCategoryId, itemCategoryOTD, user); + return res.status(200).send({ id: itemCategoryId }); + } catch (error) { + next(error); + } } /** * 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) { - const { id } = req.params; - const { ItemCategory } = req.models; + async deleteItem(req: Request, res: Response, next: NextFunction) { + const { id: itemCategoryId } = req.params; + const { tenantId, user } = req; - await ItemCategory.query() - .where('id', id) - .delete(); - - return res.status(200).send({ id }); + try { + await this.itemCategoriesService.deleteItemCategory(tenantId, itemCategoryId, user); + return res.status(200).send({ id: itemCategoryId }); + } catch (error) { + next(error); + } } /** * 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) { - + async getList(req: Request, res: Response, next: NextFunction) { + const { tenantId, user } = req; + const itemCategoriesFilter = this.matchedQueryData(req); + + try { + const itemCategories = await this.itemCategoriesService.getItemCategoriesList( + tenantId, itemCategoriesFilter, user, + ); + return res.status(200).send({ item_categories: itemCategories }); + } catch (error) { + next(error); + } } /** * 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) { + async getCategory(req: Request, res: Response, next: NextFunction) { const itemCategoryId: number = req.params.id; - const { ItemCategory } = req.models; + const { tenantId, user } = req; - const itemCategory = await ItemCategory.query().findById(itemCategoryId); - - return res.status(200).send({ category: itemCategory }); + try { + const itemCategory = await this.itemCategoriesService.getItemCategory(tenantId, itemCategoryId, user); + return res.status(200).send({ category: itemCategory }); + } catch (error) { + next(error); + } } /** * 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) { - const ids = req.query.ids; - const { ItemCategory } = req.models; + async bulkDeleteCategories(req: Request, res: Response, next: NextFunction) { + const itemCategoriesIds = req.query.ids; + const { tenantId, user } = req; - await ItemCategory.query().whereIn('id', ids).delete(); + try { + await this.itemCategoriesService.deleteItemCategories(tenantId, itemCategoriesIds, user); + return res.status(200).send({ ids: itemCategoriesIds }); + } catch (error) { + next(error); + } + } - return res.status(200).send({ ids: filter.ids }); + /** + * Handles service error. + * @param {Error} error + * @param {Request} req - + * @param {Response} res - + * @param {NextFunction} next + */ + handlerServiceError(error: Error, req: Request, res: Response, next: NextFunction) { + if (error instanceof ServiceError) { + if (error.errorType === 'CATEGORY_NOT_FOUND') { + return res.boom.badRequest(null, { + errors: [{ type: 'ITEM_CATEGORY_NOT_FOUND', code: 100 }], + }); + } + if (error.errorType === 'ITEM_CATEGORIES_NOT_FOUND') { + return res.boom.badRequest(null, { + errors: [{ type: 'ITEM_CATEGORIES_NOT_FOUND', code: 120 }], + }); + } + if (error.errorType === 'COST_ACCOUNT_NOT_FOUMD') { + return res.boom.badRequest(null, { + errors: [{ type: 'COST.ACCOUNT.NOT.FOUND', code: 120 }], + }); + } + if (error.errorType === 'COST_ACCOUNT_NOT_COGS') { + return res.boom.badRequest(null, { + errors: [{ type: 'COST.ACCOUNT.NOT.COGS.TYPE', code: 220 }], + }); + } + if (error.errorType === 'SELL_ACCOUNT_NOT_INCOME') { + return res.boom.badRequest(null, { + errors: [{ type: 'SELL.ACCOUNT.NOT.FOUND', code: 130 }], + }); + } + if (error.errorType === 'SELL_ACCOUNT_NOT_FOUND') { + return res.boom.badRequest(null, { + errors: [{ type: 'SELL.ACCOUNT.NOT.INCOME.TYPE', code: 230 }], + }); + } + if (error.errorType === 'INVENTORY_ACCOUNT_NOT_FOUND') { + return res.boom.badRequest(null, { + errors: [{ type: 'INVENTORY.ACCOUNT.NOT.FOUND', code: 200}], + }); + } + if (error.errorType === 'INVENTORY_ACCOUNT_NOT_INVENTORY') { + return res.boom.badRequest(null, { + errors: [{ type: 'INVENTORY.ACCOUNT.NOT.CURRENT.ASSET', code: 300 }], + }); + } + } + next(error); } }; diff --git a/server/src/interfaces/ItemCategory.ts b/server/src/interfaces/ItemCategory.ts index a47655a23..6da3f1ce4 100644 --- a/server/src/interfaces/ItemCategory.ts +++ b/server/src/interfaces/ItemCategory.ts @@ -1,7 +1,8 @@ - - +import { IDynamicListFilterDTO } from "./DynamicFilter"; +import { ISystemUser } from "./User"; export interface IItemCategory { + id: number, name: string, parentCategoryId?: number, @@ -27,4 +28,19 @@ export interface IItemCategoryOTD { inventoryAccountId?: number, costMethod?: string, -}; \ No newline at end of file +}; + +export interface IItemCategoriesFilter extends IDynamicListFilterDTO { + stringifiedFilterRoles?: string, +} + +export interface IItemCategoriesService { + newItemCategory(tenantId: number, itemCategoryOTD: IItemCategoryOTD, authorizedUser: ISystemUser): Promise; + editItemCategory(tenantId: number, itemCategoryId: number, itemCategoryOTD: IItemCategoryOTD, authorizedUser: ISystemUser): Promise; + + deleteItemCategory(tenantId: number, itemCategoryId: number, authorizedUser: ISystemUser): Promise; + deleteItemCategories(tenantId: number, itemCategoriesIds: number[], authorizedUser: ISystemUser): Promise; + + getItemCategory(tenantId: number, itemCategoryId: number, authorizedUser: ISystemUser): Promise; + getItemCategoriesList(tenantId: number, itemCategoriesFilter: IItemCategoriesFilter, authorizedUser: ISystemUser): Promise; +} \ No newline at end of file diff --git a/server/src/services/ItemCategories/ItemCategoriesService.ts b/server/src/services/ItemCategories/ItemCategoriesService.ts new file mode 100644 index 000000000..cbc8a0ff2 --- /dev/null +++ b/server/src/services/ItemCategories/ItemCategoriesService.ts @@ -0,0 +1,254 @@ +import { Inject } from 'typedi'; +import { difference } from 'lodash'; +import { ServiceError } from 'exceptions'; +import { + IItemCategory, + IItemCategoryOTD, + IItemCategoriesService, + IItemCategoriesFilter, + ISystemUser, +} from "interfaces"; +import ItemCategory from "models/ItemCategory"; +import DynamicListingService from 'services/DynamicListing/DynamicListService'; +import TenancyService from 'services/Tenancy/TenancyService'; + +const ERRORS = { + ITEM_CATEGORIES_NOT_FOUND: 'ITEM_CATEGORIES_NOT_FOUND', + CATEGORY_NOT_FOUND: 'CATEGORY_NOT_FOUND', + COST_ACCOUNT_NOT_FOUMD: 'COST_ACCOUNT_NOT_FOUMD', + COST_ACCOUNT_NOT_COGS: 'COST_ACCOUNT_NOT_COGS', + SELL_ACCOUNT_NOT_INCOME: 'SELL_ACCOUNT_NOT_INCOME', + SELL_ACCOUNT_NOT_FOUND: 'SELL_ACCOUNT_NOT_FOUND', + INVENTORY_ACCOUNT_NOT_FOUND: 'INVENTORY_ACCOUNT_NOT_FOUND', + INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY', +}; + +export default class ItemCategoriesService implements IItemCategoriesService { + @Inject() + tenancy: TenancyService; + + @Inject() + dynamicListService: DynamicListingService; + + @Inject('logger') + logger: any; + + /** + * Retrieve item category or throw not found error. + * @param {number} tenantId + * @param {number} itemCategoryId + */ + private async getItemCategoryOrThrowError(tenantId: number, itemCategoryId: number) { + const { ItemCategory } = this.tenancy.models(tenantId); + const category = await ItemCategory.query().findById(itemCategoryId); + + if (!category) { + throw new ServiceError(ERRORS.CATEGORY_NOT_FOUND) + } + return category; + } + + /** + * Transforms OTD to model object. + * @param {IItemCategoryOTD} itemCategoryOTD + * @param {ISystemUser} authorizedUser + */ + private transformOTDToObject(itemCategoryOTD: IItemCategoryOTD, authorizedUser: ISystemUser) { + return { ...itemCategoryOTD, userId: authorizedUser.id }; + } + + /** + * Retrieve item category of the given id. + * @param {number} tenantId - + * @param {number} itemCategoryId - + * @returns {IItemCategory} + */ + public async getItemCategory(tenantId: number, itemCategoryId: number, user: ISystemUser) { + return this.getItemCategoryOrThrowError(tenantId, itemCategoryId); + } + + /** + * Inserts a new item category. + * @param {number} tenantId + * @param {IItemCategoryOTD} itemCategoryOTD + * @return {Promise} + */ + public async newItemCategory(tenantId: number, itemCategoryOTD: IItemCategoryOTD, authorizedUser: ISystemUser): Promise { + const { ItemCategory } = this.tenancy.models(tenantId); + this.logger.info('[item_category] trying to insert a new item category.', { tenantId }); + + if (itemCategoryOTD.sellAccountId) { + await this.validateSellAccount(tenantId, itemCategoryOTD.sellAccountId); + } + if (itemCategoryOTD.costAccountId) { + await this.validateCostAccount(tenantId, itemCategoryOTD.costAccountId); + } + if (itemCategoryOTD.inventoryAccountId) { + await this.validateInventoryAccount(tenantId, itemCategoryOTD.inventoryAccountId); + } + if (itemCategoryOTD.parentCategoryId) { + await this.getItemCategoryOrThrowError(tenantId, itemCategoryOTD.parentCategoryId); + } + + const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser); + const itemCategory = await ItemCategory.query().insert({ ...itemCategoryObj }); + this.logger.info('[item_category] item category inserted successfully.', { tenantId, itemCategoryOTD }); + + return itemCategory; + } + + /** + * Validates sell account existance and type. + * @param {number} tenantId - Tenant id. + * @param {number} sellAccountId - Sell account id. + * @return {Promise} + */ + private async validateSellAccount(tenantId: number, sellAccountId: number) { + const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[items] validate sell account existance.', { tenantId, sellAccountId }); + const incomeType = await accountTypeRepository.getByKey('income'); + const foundAccount = await accountRepository.getById(sellAccountId); + + if (!foundAccount) { + 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 }); + throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_INCOME); + } + } + + /** + * Validates COGS account existance and type. + * @param {number} tenantId - + * @param {number} costAccountId - + * @return {Promise} + */ + private async validateCostAccount(tenantId: number, costAccountId: number) { + const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[items] validate cost account existance.', { tenantId, costAccountId }); + const COGSType = await accountTypeRepository.getByKey('cost_of_goods_sold'); + const foundAccount = await accountRepository.getById(costAccountId) + + if (!foundAccount) { + 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 }); + throw new ServiceError(ERRORS.COST_ACCOUNT_NOT_COGS); + } + } + + /** + * Validates inventory account existance and type. + * @param {number} tenantId + * @param {number} inventoryAccountId + * @return {Promise} + */ + private async validateInventoryAccount(tenantId: number, inventoryAccountId: number) { + const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[items] validate inventory account existance.', { tenantId, inventoryAccountId }); + const otherAsset = await accountTypeRepository.getByKey('other_asset'); + const foundAccount = await accountRepository.getById(inventoryAccountId); + + if (!foundAccount) { + 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 }); + throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_INVENTORY); + } + } + + /** + * Edits item category. + * @param {number} tenantId + * @param {number} itemCategoryId + * @param {IItemCategoryOTD} itemCategoryOTD + * @return {Promise} + */ + public async editItemCategory(tenantId: number, itemCategoryId: number, itemCategoryOTD: IItemCategoryOTD, authorizedUser: ISystemUser): Promise { + const oldItemCategory = await this.getItemCategoryOrThrowError(tenantId, itemCategoryId); + + if (itemCategoryOTD.sellAccountId) { + await this.validateSellAccount(tenantId, itemCategoryOTD.sellAccountId); + } + if (itemCategoryOTD.costAccountId) { + await this.validateCostAccount(tenantId, itemCategoryOTD.costAccountId); + } + if (itemCategoryOTD.inventoryAccountId) { + await this.validateInventoryAccount(tenantId, itemCategoryOTD.inventoryAccountId); + } + if (itemCategoryOTD.parentCategoryId) { + await this.getItemCategoryOrThrowError(tenantId, itemCategoryOTD.parentCategoryId); + } + + const itemCategoryObj = this.transformOTDToObject(itemCategoryOTD, authorizedUser); + const itemCategory = await ItemCategory.query().patchAndFetchById(itemCategoryId, { ...itemCategoryObj }); + this.logger.info('[item_category] edited successfully.', { tenantId, itemCategoryId, itemCategoryOTD }); + + return itemCategory; + } + + /** + * Deletes the given item category. + * @param {number} tenantId - Tenant id. + * @param {number} itemCategoryId - Item category id. + * @return {Promise} + */ + public async deleteItemCategory(tenantId: number, itemCategoryId: number, authorizedUser: ISystemUser) { + this.logger.info('[item_category] trying to delete item category.', { tenantId, itemCategoryId }); + await this.getItemCategoryOrThrowError(tenantId, itemCategoryId); + + const { ItemCategory } = this.tenancy.models(tenantId); + await ItemCategory.query().findById(itemCategoryId).delete(); + this.logger.info('[item_category] deleted successfully.', { tenantId, itemCategoryId }); + } + + /** + * Retrieve item categories or throw not found error. + * @param {number} tenantId + * @param {number[]} itemCategoriesIds + */ + private async getItemCategoriesOrThrowError(tenantId: number, itemCategoriesIds: number[]) { + const itemCategories = await ItemCategory.query().whereIn('id', ids); + const storedItemCategoriesIds = itemCategories.map((category: IItemCategory) => category.id); + const notFoundCategories = difference(itemCategoriesIds, storedItemCategoriesIds); + + if (notFoundCategories.length > 0) { + throw new ServiceError(ERRORS.ITEM_CATEGORIES_NOT_FOUND); + } + } + + /** + * Retrieve item categories list. + * @param {number} tenantId + * @param filter + */ + public async getItemCategoriesList(tenantId: number, filter: IItemCategoriesFilter, authorizedUser: ISystemUser) { + const { ItemCategory } = this.tenancy.models(tenantId); + const dynamicList = await this.dynamicListService.dynamicList(tenantId, ItemCategory, filter); + + const itemCategories = await ItemCategory.query().onBuild((query) => { + query.orderBy('createdAt', 'ASC'); + dynamicList.buildQuery()(query); + }); + return itemCategories; + } + + /** + * Deletes item categories in bulk. + * @param {number} tenantId + * @param {number[]} itemCategoriesIds + */ + public async deleteItemCategories(tenantId: number, itemCategoriesIds: number[], authorizedUser: ISystemUser) { + this.logger.info('[item_category] trying to delete item categories.', { tenantId, itemCategoriesIds }); + await this.getItemCategoriesOrThrowError(tenantId, itemCategoriesIds); + + await ItemCategory.query().whereIn('id', itemCategoriesIds).delete(); + this.logger.info('[item_category] item categories deleted successfully.', { tenantId, itemCategoriesIds }); + } +} \ No newline at end of file