refactor: item categories service.

This commit is contained in:
Ahmed Bouhuolia
2020-09-30 10:47:20 +02:00
parent 35fce02c7a
commit 8f90b75ccd
3 changed files with 428 additions and 228 deletions

View File

@@ -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<void>}
*/
public async newItemCategory(tenantId: number, itemCategoryOTD: IItemCategoryOTD, authorizedUser: ISystemUser): Promise<IItemCategory> {
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<void>}
*/
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<void>}
*/
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<void>}
*/
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<void>}
*/
public async editItemCategory(tenantId: number, itemCategoryId: number, itemCategoryOTD: IItemCategoryOTD, authorizedUser: ISystemUser): Promise<IItemCategory> {
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<void>}
*/
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 });
}
}