import { Injectable, NotFoundException } 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 { Invoice } from '../invoice/entities/invoice.entity'; 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'; import { ChattService } from '../chat/providers/chat.service'; @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, private readonly chatService: ChattService, ) { } 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, }); em.persist(request); for (const item of items) { const product = await this.productService.findOneOrFail(item.productId); const requestItem = em.create(RequestItem, { request, product, 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'); } 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, 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, 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) { return this.em.transactional(async (em) => { const request = await em.findOne(Request, { id, user: { id: userId } }); if (!request) { throw new NotFoundException('Request not found'); } await this.hardDeleteRequest(id, em); return { message: 'Request deleted successfully' }; }); } async removeAsAdmin(id: string) { return this.em.transactional(async (em) => { const request = await em.findOne(Request, { id }); if (!request) { throw new NotFoundException('Request not found'); } await this.hardDeleteRequest(id, em); return { message: 'Request deleted successfully' }; }); } private async hardDeleteRequest(id: string, em: EntityManager) { await this.chatService.deleteChatsByRefId(id, em); await em.nativeUpdate(Invoice, { request: { id } }, { request: null }); await em.nativeDelete(RequestItem, { request: { id } }); await em.nativeDelete(Request, { id }); } }