From 3ac6d8897ec69b37a9f6b63dcf6e5fa5e3813adf Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 16 Dec 2020 16:38:18 +0200 Subject: [PATCH] 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 +}