refactor: items services to Nestjs

This commit is contained in:
Ahmed Bouhuolia
2024-12-15 13:04:41 +02:00
parent 70211980aa
commit 0a112c5655
20 changed files with 897 additions and 18 deletions

View File

@@ -0,0 +1,41 @@
import { Inject, Injectable } from '@nestjs/common';
import { Item } from './models/Item';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { Knex } from 'knex';
@Injectable()
export class ActivateItemService {
constructor(
@Inject(Item.name)
private readonly itemModel: typeof Item,
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
) {}
/**
* Activates the given item on the storage.
* @param {number} itemId -
* @return {Promise<void>}
*/
public async activateItem(
itemId: number,
trx?: Knex.Transaction,
): Promise<void> {
// Retrieves the given item or throw not found error.
const oldItem = await this.itemModel
.query()
.findById(itemId)
.throwIfNotFound();
// Activate the given item with associated transactions under unit-of-work environment.
return this.uow.withTransaction(async (trx) => {
// Mutate item on the storage.
await this.itemModel.query(trx).findById(itemId).patch({ active: true });
// Triggers `onItemActivated` event.
await this.eventEmitter.emitAsync(events.item.onActivated, {});
}, trx);
}
}

View File

@@ -7,7 +7,6 @@ import { events } from '@/common/events/events';
import { ItemsValidators } from './ItemValidator.service';
import { Item } from './models/Item';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable({ scope: Scope.REQUEST })
export class CreateItemService {
@@ -90,17 +89,17 @@ export class CreateItemService {
/**
* Creates a new item.
* @param {IItemDTO} itemDTO
* @return {Promise<IItem>}
* @return {Promise<number>} - The created item id.
*/
public async createItem(
itemDTO: IItemDTO,
trx?: Knex.Transaction,
): Promise<Item> {
): Promise<number> {
// 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<Item>(async (trx: Knex.Transaction) => {
return this.uow.withTransaction<number>(async (trx: Knex.Transaction) => {
const itemInsert = this.transformNewItemDTOToModel(itemDTO);
// Inserts a new item and fetch the created item.
@@ -114,7 +113,7 @@ export class CreateItemService {
trx,
} as IItemEventCreatedPayload);
return item;
return item.id;
}, trx);
}
}

View File

@@ -100,14 +100,15 @@ export class EditItemService {
/**
* Edits the item metadata.
* @param {number} itemId
* @param {IItemDTO} itemDTO
* @param {number} itemId - The item id.
* @param {IItemDTO} itemDTO - The item DTO.
* @return {Promise<number>} - The updated item id.
*/
public async editItem(
itemId: number,
itemDTO: IItemDTO,
trx?: Knex.Transaction,
): Promise<Item> {
): Promise<number> {
// Validates the given item existance on the storage.
const oldItem = await this.itemModel
.query()
@@ -121,7 +122,7 @@ export class EditItemService {
const itemModel = this.transformEditItemDTOToModel(itemDTO, oldItem);
// Edits the item with associated transactions under unit-of-work environment.
return this.uow.withTransaction<Item>(async (trx: Knex.Transaction) => {
return this.uow.withTransaction<number>(async (trx: Knex.Transaction) => {
// Updates the item on the storage and fetches the updated one.
const newItem = await this.itemModel
.query(trx)
@@ -137,7 +138,7 @@ export class EditItemService {
// Triggers `onItemEdited` event.
await this.eventEmitter.emitAsync(events.item.onEdited, eventPayload);
return newItem;
return newItem.id;
}, trx);
}
}

View File

@@ -0,0 +1,46 @@
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Inject, Injectable } from '@nestjs/common';
import { Item } from './models/Item';
import { events } from '@/common/events/events';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { ItemTransformer } from './Item.transformer';
@Injectable()
export class GetItemService {
constructor(
@Inject(Item)
private itemModel: typeof Item,
private eventEmitter2: EventEmitter2,
private transformerInjectable: TransformerInjectable,
) {}
/**
* Retrieve the item details of the given id with associated details.
* @param {number} tenantId - The tenant id.
* @param {number} itemId - The item id.
*/
public async getItem(itemId: number): Promise<any> {
const item = await this.itemModel
.query()
.findById(itemId)
.withGraphFetched('sellAccount')
.withGraphFetched('inventoryAccount')
.withGraphFetched('category')
.withGraphFetched('costAccount')
.withGraphFetched('itemWarehouses.warehouse')
.withGraphFetched('sellTaxRate')
.withGraphFetched('purchaseTaxRate')
.throwIfNotFound();
const transformed = await this.transformerInjectable.transform(
item,
new ItemTransformer(),
);
const eventPayload = { itemId };
// Triggers the `onItemViewed` event.
await this.eventEmitter2.emitAsync(events.item.onViewed, eventPayload);
return transformed;
}
}

View File

@@ -0,0 +1,40 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Item } from './models/Item';
import { events } from '@/common/events/events';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
@Injectable()
export class InactivateItem {
constructor(
@Inject(Item.name) private itemModel: typeof Item,
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
) {}
/**
* Inactivates the given item on the storage.
* @param {number} itemId
* @return {Promise<void>}
*/
public async inactivateItem(
itemId: number,
trx?: Knex.Transaction,
): Promise<void> {
// Retrieves the item or throw not found error.
const oldItem = await this.itemModel
.query()
.findById(itemId)
.throwIfNotFound();
// Inactivate item under unit-of-work environment.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Inactivate item on the storage.
await this.itemModel.query(trx).findById(itemId).patch({ active: false });
// Triggers `onItemInactivated` event.
await this.eventEmitter.emitAsync(events.item.onInactivated, { trx });
}, trx);
}
}

View File

@@ -6,16 +6,18 @@ import {
Post,
UsePipes,
UseGuards,
Patch,
Get,
} from '@nestjs/common';
import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
import { createItemSchema } from './Item.schema';
import { CreateItemService } from './CreateItem.service';
import { Item } from './models/Item';
import { DeleteItemService } from './DeleteItem.service';
import { TenantController } from '../Tenancy/Tenant.controller';
import { SubscriptionGuard } from '../Subscription/interceptors/Subscription.guard';
import { ClsService } from 'nestjs-cls';
import { PublicRoute } from '../Auth/Jwt.guard';
import { EditItemService } from './EditItem.service';
import { ItemsApplicationService } from './ItemsApplication.service';
@Controller('/items')
@UseGuards(SubscriptionGuard)
@@ -24,20 +26,76 @@ export class ItemsController extends TenantController {
constructor(
private readonly createItemService: CreateItemService,
private readonly deleteItemService: DeleteItemService,
private readonly cls: ClsService,
private readonly editItemService: EditItemService,
private readonly itemsApplication: ItemsApplicationService,
) {
super();
}
/**
* Edit item.
* @param id - The item id.
* @param editItemDto - The item DTO.
* @returns The updated item id.
*/
@Post(':id')
@UsePipes(new ZodValidationPipe(createItemSchema))
async editItem(
@Param('id') id: string,
@Body() editItemDto: any,
): Promise<number> {
const itemId = parseInt(id, 10);
return this.editItemService.editItem(itemId, editItemDto);
}
/**
* Create item.
* @param createItemDto - The item DTO.
* @returns The created item id.
*/
@Post()
@UsePipes(new ZodValidationPipe(createItemSchema))
async createItem(@Body() createItemDto: any): Promise<Item> {
async createItem(@Body() createItemDto: any): Promise<number> {
return this.createItemService.createItem(createItemDto);
}
/**
* Delete item.
* @param id - The item id.
*/
@Delete(':id')
async deleteItem(@Param('id') id: string): Promise<void> {
const itemId = parseInt(id, 10);
return this.deleteItemService.deleteItem(itemId);
}
/**
* Inactivate item.
* @param id - The item id.
*/
@Patch(':id/inactivate')
async inactivateItem(@Param('id') id: string): Promise<void> {
const itemId = parseInt(id, 10);
return this.itemsApplication.inactivateItem(itemId);
}
/**
* Activate item.
* @param id - The item id.
*/
@Patch(':id/activate')
async activateItem(@Param('id') id: string): Promise<void> {
const itemId = parseInt(id, 10);
return this.itemsApplication.activateItem(itemId);
}
/**
* Get item.
* @param id - The item id.
*/
@Get(':id')
async getItem(@Param('id') id: string): Promise<any> {
const itemId = parseInt(id, 10);
return this.itemsApplication.getItem(itemId);
}
}

View File

@@ -108,4 +108,5 @@ export const createItemSchema = z
);
export type createItemDTO = z.infer<typeof createItemSchema>;
export type createItemDTO = z.infer<typeof createItemSchema>;
export type editItemDTOSchema = z.infer<typeof createItemSchema>;

View File

@@ -0,0 +1,61 @@
import { Transformer } from '../Transformer/Transformer';
// import { GetItemWarehouseTransformer } from '@/services/Warehouses/Items/GettItemWarehouseTransformer';
export class ItemTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'typeFormatted',
'sellPriceFormatted',
'costPriceFormatted',
'itemWarehouses',
];
};
/**
* Formatted item type.
* @param {IItem} item
* @returns {string}
*/
public typeFormatted(item): string {
return this.context.i18n.t(`item.field.type.${item.type}`);
}
/**
* Formatted sell price.
* @param item
* @returns {string}
*/
public sellPriceFormatted(item): string {
return this.formatNumber(item.sellPrice, {
currencyCode: this.context.organization.baseCurrency,
});
}
/**
* Formatted cost price.
* @param item
* @returns {string}
*/
public costPriceFormatted(item): string {
return this.formatNumber(item.costPrice, {
currencyCode: this.context.organization.baseCurrency,
});
}
/**
* Associate the item warehouses quantity.
* @param item
* @returns
*/
// public itemWarehouses = (item) => {
// return this.item(
// item.itemWarehouses,
// new GetItemWarehouseTransformer(),
// {},
// );
// };
}

View File

@@ -5,6 +5,10 @@ import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
import { ItemsValidators } from './ItemValidator.service';
import { DeleteItemService } from './DeleteItem.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { EditItemService } from './EditItem.service';
import { InactivateItem } from './InactivateItem.service';
import { ActivateItemService } from './ActivateItem.service';
import { ItemsApplicationService } from './ItemsApplication.service';
@Module({
imports: [TenancyDatabaseModule],
@@ -12,7 +16,11 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service';
providers: [
ItemsValidators,
CreateItemService,
EditItemService,
InactivateItem,
ActivateItemService,
DeleteItemService,
ItemsApplicationService,
TenancyContext,
],
})

View File

@@ -0,0 +1,84 @@
import { Item } from './models/Item';
import { CreateItemService } from './CreateItem.service';
import { DeleteItemService } from './DeleteItem.service';
import { EditItemService } from './EditItem.service';
import { IItem, IItemDTO } from '@/interfaces/Item';
import { Knex } from 'knex';
import { InactivateItem } from './InactivateItem.service';
import { ActivateItemService } from './ActivateItem.service';
import { GetItemService } from './GetItem.service';
export class ItemsApplicationService {
constructor(
private readonly createItemService: CreateItemService,
private readonly editItemService: EditItemService,
private readonly deleteItemService: DeleteItemService,
private readonly activateItemService: ActivateItemService,
private readonly inactivateItemService: InactivateItem,
private readonly getItemService: GetItemService,
) {}
/**
* Creates a new item.
* @param {IItemDTO} createItemDto - The item DTO.
* @param {Knex.Transaction} [trx] - The transaction.
* @return {Promise<number>} - The created item id.
*/
async createItem(
createItemDto: IItemDTO,
trx?: Knex.Transaction,
): Promise<number> {
return this.createItemService.createItem(createItemDto);
}
/**
* Edits an existing item.
* @param {number} itemId - The item id.
* @param {IItemDTO} editItemDto - The item DTO.
* @param {Knex.Transaction} [trx] - The transaction.
* @return {Promise<number>} - The updated item id.
*/
async editItem(
itemId: number,
editItemDto: IItemDTO,
trx?: Knex.Transaction,
): Promise<number> {
return this.editItemService.editItem(itemId, editItemDto, trx);
}
/**
* Deletes an existing item.
* @param {number} itemId - The item id.
* @return {Promise<void>}
*/
async deleteItem(itemId: number): Promise<void> {
return this.deleteItemService.deleteItem(itemId);
}
/**
* Activates an item.
* @param {number} itemId - The item id.
* @returns {Promise<void>}
*/
async activateItem(itemId: number): Promise<void> {
return this.activateItemService.activateItem(itemId);
}
/**
* Inactivates an item.
* @param {number} itemId - The item id.
* @returns {Promise<void>}
*/
async inactivateItem(itemId: number): Promise<void> {
return this.inactivateItemService.inactivateItem(itemId);
}
/**
* Retrieves the item details of the given id with associated details.
* @param {number} itemId - The item id.
* @returns {Promise<IItem>} - The item details.
*/
async getItem(itemId: number): Promise<any> {
return this.getItemService.getItem(itemId);
}
}