import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { CreateRequestDto } from './dto/create-request.dto'; import { UpdateRequestDto } from './dto/update-request.dto'; import { FindRequestsDto } from './dto/find-requests.dto'; import { Request } from './entities/request.entity'; import { RequestItem } from './entities/request-item.entity'; import { RequestStatusEnum } from './enum/request'; import { UserService } from '../user/providers/user.service'; import { ProductService } from '../product/providers/product.service'; import { FieldRepository } from '../form-builder/repository/field.repository'; import { RequestRepository } from './repositories/request.repository'; import { RequestCreatedEvent } from './events/request.events'; @Injectable() export class RequestService { constructor( private readonly em: EntityManager, private readonly requestRepository: RequestRepository, private readonly userService: UserService, private readonly productService: ProductService, private readonly fieldRepository: FieldRepository, private readonly eventEmitter: EventEmitter2, ) { } async create(createRequestDto: CreateRequestDto, userId: string) { const { items } = createRequestDto; const user = await this.userService.findOrFail(userId); const request = await this.em.transactional(async (em) => { const request = em.create(Request, { user, status: RequestStatusEnum.PENDING, }); em.persist(request); for (const item of items) { const product = await this.productService.findOneOrFail(item.productId); const requestItem = em.create(RequestItem, { request, product, quantity: item.quantity, attributes: item.attributes, description: item.description, attachments: item.attachments, }); request.items.add(requestItem); } await em.flush(); return request; }); this.eventEmitter.emit( RequestCreatedEvent.name, new RequestCreatedEvent(request.id, request.requestNumber, user.id), ); return request; } findAll(userId: string, dto: FindRequestsDto) { return this.requestRepository.findAllPaginated({ userId, ...dto }); } findAllAsAdmin(dto: FindRequestsDto) { return this.requestRepository.findAllPaginated(dto); } async findOne(id: string, userId: string) { const request = await this.requestRepository.findOne( { id, user: { id: userId } }, { populate: ['items', 'items.product', 'user'] }, ); if (!request) { throw new NotFoundException('Request not found'); } await this.enrichItemsWithFields(request.items.getItems()); return request; } async findOneAsAdmin(id: string) { const request = await this.requestRepository.findOne( { id }, { populate: ['items', 'items.product', 'user'] }, ); if (!request) { throw new NotFoundException('Request not found'); } await this.enrichItemsWithFields(request.items.getItems()); return request; } private async enrichItemsWithFields(items: RequestItem[]) { const fieldIds = [ ...new Set( items .flatMap((item) => item.attributes ?? []) .map((attr) => String(attr.fieldId)) .filter(Boolean), ), ]; console.log(fieldIds); if (fieldIds.length === 0) return; const fields = await this.fieldRepository.find( { id: { $in: fieldIds } }, { populate: ['options'] }, ); console.log('fields',fields) const fieldMap = new Map(fields.map((f) => [f.id, f])); for (const item of items) { if (!item.attributes?.length) continue; const enrichedAttributes = item.attributes.map((attr) => ({ ...attr, field: fieldMap.get(String(attr.fieldId)) ?? null, })); (item as unknown as { attributes: typeof enrichedAttributes }).attributes = enrichedAttributes; } } async update(id: string, updateRequestDto: UpdateRequestDto, userId: string) { const request = await this.requestRepository.findOne( { id, user: { id: userId } }, { populate: ['items', 'items.product', 'user'] }, ); if (!request) { throw new NotFoundException('Request not found'); } if (request.status !== RequestStatusEnum.PENDING) { throw new BadRequestException('Only pending requests can be updated'); } const { items } = updateRequestDto; if (items?.length) { return this.em.transactional(async (em) => { request.items.removeAll(); await em.flush(); for (const item of items) { const product = await this.productService.findOneOrFail(item.productId); const requestItem = em.create(RequestItem, { request, product, quantity: item.quantity, attributes: item.attributes, description: item.description, attachments: item.attachments, }); request.items.add(requestItem); } await em.flush(); return request; }); } return request; } async updateAsAdmin(id: string, updateRequestDto: UpdateRequestDto) { const request = await this.requestRepository.findOne( { id }, { populate: ['items', 'items.product', 'user'] }, ); if (!request) { throw new NotFoundException('Request not found'); } const { items } = updateRequestDto; if (items?.length) { return this.em.transactional(async (em) => { request.items.removeAll(); await em.flush(); for (const item of items) { const product = await this.productService.findOneOrFail(item.productId); const requestItem = em.create(RequestItem, { request, product, quantity: item.quantity, attributes: item.attributes, description: item.description, attachments: item.attachments, }); request.items.add(requestItem); } await em.flush(); return request; }); } return request; } async remove(id: string, userId: string) { const request = await this.requestRepository.findOne( { id, user: { id: userId } }, ); if (!request) { throw new NotFoundException('Request not found'); } if (request.status !== RequestStatusEnum.PENDING) { throw new BadRequestException('Only pending requests can be deleted'); } request.deletedAt = new Date(); await this.em.flush(); return request; } async removeAsAdmin(id: string) { const request = await this.requestRepository.findOne( { id }, ); if (!request) { throw new NotFoundException('Request not found'); } request.deletedAt = new Date(); await this.em.flush(); return request; } }