import { Knex } from 'knex'; import { defaultTo } from 'lodash'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Inject, Injectable, Scope } from '@nestjs/common'; import { IItemDTO, IItemEventCreatedPayload } from '@/interfaces/Item'; import { events } from '@/common/events/events'; import { ItemsValidators } from './ItemValidator.service'; import { Item } from './models/Item'; import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; import { CreateItemDto } from './dtos/Item.dto'; @Injectable({ scope: Scope.REQUEST }) export class CreateItemService { /** * Constructor for the CreateItemService class. * @param {EventEmitter2} eventEmitter - Event emitter for publishing item creation events. * @param {UnitOfWork} uow - Unit of Work for tenant database transactions. * @param {ItemsValidators} validators - Service for validating item data. * @param {typeof Item} itemModel - The Item model class for database operations. */ constructor( private readonly eventEmitter: EventEmitter2, private readonly uow: UnitOfWork, private readonly validators: ItemsValidators, @Inject(Item.name) private readonly itemModel: TenantModelProxy, ) {} /** * Authorize the creating item. * @param {number} tenantId * @param {IItemDTO} itemDTO */ async authorize(itemDTO: CreateItemDto) { // Validate whether the given item name already exists on the storage. await this.validators.validateItemNameUniquiness(itemDTO.name); if (itemDTO.categoryId) { await this.validators.validateItemCategoryExistance(itemDTO.categoryId); } if (itemDTO.sellAccountId) { await this.validators.validateItemSellAccountExistance( itemDTO.sellAccountId, ); } // Validate the income account id existance if the item is sellable. this.validators.validateIncomeAccountExistance( itemDTO.sellable, itemDTO.sellAccountId, ); if (itemDTO.costAccountId) { await this.validators.validateItemCostAccountExistance( itemDTO.costAccountId, ); } // Validate the cost account id existance if the item is purchasable. this.validators.validateCostAccountExistance( itemDTO.purchasable, itemDTO.costAccountId, ); if (itemDTO.inventoryAccountId) { await this.validators.validateItemInventoryAccountExistance( itemDTO.inventoryAccountId, ); } if (itemDTO.purchaseTaxRateId) { await this.validators.validatePurchaseTaxRateExistance( itemDTO.purchaseTaxRateId, ); } if (itemDTO.sellTaxRateId) { await this.validators.validateSellTaxRateExistance(itemDTO.sellTaxRateId); } } /** * Transforms the item DTO to model. * @param {CreateItemDto} itemDTO - Item DTO. * @return {IItem} */ private transformNewItemDTOToModel(itemDTO: CreateItemDto) { return { ...itemDTO, active: Boolean(defaultTo(itemDTO.active, true)), quantityOnHand: itemDTO.type === 'inventory' ? 0 : null, }; } /** * Creates a new item. * @param {IItemDTO} itemDTO * @return {Promise} - The created item id. */ public async createItem( itemDTO: CreateItemDto, trx?: Knex.Transaction, ): Promise { // Authorize the item before creating. await this.authorize(itemDTO); // Creates a new item with associated transactions under unit-of-work envirement. return this.uow.withTransaction(async (trx: Knex.Transaction) => { const itemInsert = this.transformNewItemDTOToModel(itemDTO); // Inserts a new item and fetch the created item. const item = await this.itemModel() .query(trx) .insertAndFetch({ ...itemInsert, }); // Triggers `onItemCreated` event. await this.eventEmitter.emitAsync(events.item.onCreated, { item, itemId: item.id, trx, } as IItemEventCreatedPayload); return item.id; }, trx); } }