From 5f97aa63049760c2a5a2d050e4b6c2ed92cb43e0 Mon Sep 17 00:00:00 2001 From: DigoCast Date: Thu, 30 Apr 2026 01:06:55 -0300 Subject: [PATCH] feat: assign agent and initialize chat when answering ticket --- backend/src/modules/chat/chat.module.ts | 2 +- .../modules/chat/domain/chat.repository.ts | 2 + .../chat/infra/chat.repository.mongodb.ts | 7 +++ .../newAgent/newAgent.usecase.spec.ts | 52 +++++++++++++++---- .../useCases/newAgent/newAgent.usecase.ts | 51 ++++++++++++------ backend/src/modules/ticket/ticket.module.ts | 2 + 6 files changed, 88 insertions(+), 28 deletions(-) diff --git a/backend/src/modules/chat/chat.module.ts b/backend/src/modules/chat/chat.module.ts index 150114c..470fe10 100644 --- a/backend/src/modules/chat/chat.module.ts +++ b/backend/src/modules/chat/chat.module.ts @@ -26,6 +26,6 @@ import { ChatController } from './presentation/chat.controller'; { provide: 'IChatRepository', useExisting: ChatRepositoryMongodb }, { provide: 'IMessageRepository', useExisting: MessageRepositoryMongodb }, ], - exports: [ChatService], + exports: [ChatService, 'IChatRepository'], }) export class ChatModule {} \ No newline at end of file diff --git a/backend/src/modules/chat/domain/chat.repository.ts b/backend/src/modules/chat/domain/chat.repository.ts index 57e03fd..52f6b9c 100644 --- a/backend/src/modules/chat/domain/chat.repository.ts +++ b/backend/src/modules/chat/domain/chat.repository.ts @@ -15,4 +15,6 @@ export interface IChatRepository { findByTicketId(ticketId: string): Promise; updateStatus(id: string, status: ChatStatus): Promise; + + addAgentToChat(ticketId: string, agentId: string): Promise; } diff --git a/backend/src/modules/chat/infra/chat.repository.mongodb.ts b/backend/src/modules/chat/infra/chat.repository.mongodb.ts index 19541b3..d8e95de 100644 --- a/backend/src/modules/chat/infra/chat.repository.mongodb.ts +++ b/backend/src/modules/chat/infra/chat.repository.mongodb.ts @@ -87,4 +87,11 @@ export class ChatRepositoryMongodb implements IChatRepository { if (!doc) return null; return this.toDetails(doc); } + + async addAgentToChat(ticketId: string, agentId: string): Promise { + await this.chatModel.updateOne( + { ticketId }, + { $set: { agentId: agentId } } + ); + } } diff --git a/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.spec.ts b/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.spec.ts index 5b18c3a..791e706 100644 --- a/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.spec.ts +++ b/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.spec.ts @@ -1,14 +1,13 @@ /* eslint-disable @typescript-eslint/unbound-method */ import { randomUUID } from 'crypto'; import { ITicketRepository } from '../../../domain/repository/ticket.repository.interface'; -import { - Ticket, - TicketStatus, -} from '../../../domain/entities/ticket.entity'; +import { Ticket, TicketStatus } from '../../../domain/entities/ticket.entity'; import { NewAgentTicketUseCase } from './newAgent.usecase'; +import type { IChatRepository } from '../../../../chat/domain/chat.repository'; describe('NewAgentTicketUseCase', () => { let repository: jest.Mocked; + let chatRepository: jest.Mocked; let useCase: NewAgentTicketUseCase; let ticket: Ticket; @@ -25,20 +24,31 @@ describe('NewAgentTicketUseCase', () => { save: jest.fn(), } as unknown as jest.Mocked; - useCase = new NewAgentTicketUseCase(repository); + chatRepository = { + create: jest.fn(), + findById: jest.fn(), + findByParticipant: jest.fn(), + findByTicketId: jest.fn(), + updateStatus: jest.fn(), + addAgentToChat: jest.fn(), + } as unknown as jest.Mocked; + + useCase = new NewAgentTicketUseCase(repository, chatRepository); }); - it('should assing a new agent to a ticket successfully', async () => { + it('should assign a new agent to a ticket successfully and create a chat if it does not exist', async () => { const input = { id: ticket.id, agentId: randomUUID(), }; repository.readById.mockResolvedValue(ticket); - ticket.assignToAgent(input.agentId); repository.save.mockResolvedValue(ticket); + chatRepository.findByTicketId.mockResolvedValue(null); + chatRepository.create.mockResolvedValue({} as any); + const output = await useCase.execute(input); expect(output).toBeDefined(); @@ -48,12 +58,34 @@ describe('NewAgentTicketUseCase', () => { expect(repository.readById).toHaveBeenCalledTimes(1); expect(repository.readById).toHaveBeenCalledWith(input.id); - expect(repository.save).toHaveBeenCalledTimes(1); - expect(repository.save).toHaveBeenCalledWith(ticket); + + expect(chatRepository.findByTicketId).toHaveBeenCalledTimes(1); + expect(chatRepository.findByTicketId).toHaveBeenCalledWith(input.id); + expect(chatRepository.create).toHaveBeenCalledTimes(1); expect(output).not.toHaveProperty('toPrimitives'); expect(output).not.toHaveProperty('escalate'); expect(output).not.toHaveProperty('assignToAgent'); }); -}); + + it('should assign a new agent to a ticket and add the agent to the existing chat', async () => { + const input = { + id: ticket.id, + agentId: randomUUID(), + }; + + repository.readById.mockResolvedValue(ticket); + ticket.assignToAgent(input.agentId); + repository.save.mockResolvedValue(ticket); + chatRepository.findByTicketId.mockResolvedValue({ id: 'chat-uuid-123' } as any); + + const output = await useCase.execute(input); + + expect(output).toBeDefined(); + expect(chatRepository.findByTicketId).toHaveBeenCalledWith(input.id); + + expect(chatRepository.addAgentToChat).toHaveBeenCalledWith(input.id, input.agentId); + expect(chatRepository.create).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.ts b/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.ts index 273f04a..8adb6b5 100644 --- a/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.ts +++ b/backend/src/modules/ticket/application/useCases/newAgent/newAgent.usecase.ts @@ -1,7 +1,8 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { TicketStatus } from '../../../domain/entities/ticket.entity'; import { ITicketRepository } from '../../../domain/repository/ticket.repository.interface'; +import type { IChatRepository } from '../../../../chat/domain/chat.repository'; export interface NewAgentTicketInput { id: string; agentId: string; @@ -13,29 +14,45 @@ export interface NewAgentTicketOutput { status: TicketStatus; } + @Injectable() export class NewAgentTicketUseCase { - constructor(private readonly repository: ITicketRepository) {} + constructor( + private readonly repository: ITicketRepository, + @Inject('IChatRepository') + private readonly chatRepository: IChatRepository + ) {} async execute(input: NewAgentTicketInput): Promise { - const foundedTicket = await this.repository.readById(input.id); - - if (!foundedTicket) { - throw new Error('Ticket not found.'); - } + const foundedTicket = await this.repository.readById(input.id); - foundedTicket.assignToAgent(input.agentId); + if (!foundedTicket) { + throw new Error('Ticket not found.'); + } + foundedTicket.assignToAgent(input.agentId); - const updatedTicket = await this.repository.save(foundedTicket); + const updatedTicket = await this.repository.save(foundedTicket); - if (!updatedTicket) { - throw new Error('Fail to update ticket.'); - } + if (!updatedTicket) { + throw new Error('Fail to update ticket.'); + } + const chat = await this.chatRepository.findByTicketId(input.id); + + if (!chat) { + await this.chatRepository.create({ + ticketId: input.id, + clientId: foundedTicket.clientId, + agentId: input.agentId, + groupId: foundedTicket.groupId ?? '', + }); + } else { + await this.chatRepository.addAgentToChat(input.id, input.agentId); + } - return { - id: updatedTicket.id, - agentId: updatedTicket.agentId, - status: updatedTicket.status, - }; + return { + id: updatedTicket.id, + agentId: updatedTicket.agentId, + status: updatedTicket.status, + }; } } diff --git a/backend/src/modules/ticket/ticket.module.ts b/backend/src/modules/ticket/ticket.module.ts index ca9bfdd..6898a3d 100644 --- a/backend/src/modules/ticket/ticket.module.ts +++ b/backend/src/modules/ticket/ticket.module.ts @@ -16,6 +16,7 @@ import { DeleteTicketUseCase } from './application/useCases/delete/delete.usecas import { NewAgentTicketUseCase } from './application/useCases/newAgent/newAgent.usecase'; import { TriageModule } from '../triage/triage.module'; import { CloseTicketUseCase } from './application/useCases/close/close.usecase'; +import { ChatModule } from '../chat/chat.module'; @Module({ imports: [ @@ -23,6 +24,7 @@ import { CloseTicketUseCase } from './application/useCases/close/close.usecase'; { name: TicketSchemaClass.name, schema: TicketSchema }, ]), TriageModule, + ChatModule, ], controllers: [TicketController], providers: [