From 11e7a119d8e70bba9ba1a2549ee9e180a55432f2 Mon Sep 17 00:00:00 2001 From: DaviMBDev Date: Sun, 24 May 2026 16:39:24 -0300 Subject: [PATCH 1/2] feat/back-front-integration --- .../modules/chat/application/chat.service.ts | 5 +++ .../chat/presentation/chat.controller.ts | 5 +++ .../controllers/ticket.controller.ts | 44 ++++++++++++++++++ .../dtos/updateTicketStatus.dto.ts | 45 +++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts diff --git a/backend/src/modules/chat/application/chat.service.ts b/backend/src/modules/chat/application/chat.service.ts index fddf75e..48cb524 100644 --- a/backend/src/modules/chat/application/chat.service.ts +++ b/backend/src/modules/chat/application/chat.service.ts @@ -69,6 +69,11 @@ export class ChatService { } } + const ticket = await this.ticketModel.findById(chat.ticketId).exec(); + if (ticket && ticket.status === 'CLOSED') { + throw new ForbiddenException('Este chamado está resolvido/fechado. Não é permitido enviar novas mensagens.'); + } + // PASSANDO OS NOVOS CAMPOS PARA O REPOSITÓRIO return this.messageRepository.create({ chatId, diff --git a/backend/src/modules/chat/presentation/chat.controller.ts b/backend/src/modules/chat/presentation/chat.controller.ts index 1dd66ac..8ccd849 100644 --- a/backend/src/modules/chat/presentation/chat.controller.ts +++ b/backend/src/modules/chat/presentation/chat.controller.ts @@ -16,4 +16,9 @@ export class ChatController { if (!chat) throw new NotFoundException('Chat não encontrado'); return chat; } + + @Get(':id') + async getChatById(@Param('id') id: string) { + return this.chatService.getChatById(id); + } } \ No newline at end of file diff --git a/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts b/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts index 1809393..bfbb27d 100644 --- a/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts +++ b/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts @@ -12,6 +12,7 @@ import { Request, UseGuards, Query, + BadRequestException, } from '@nestjs/common'; import { CreateTicketUseCase } from '../../application/useCases/create/create.usecase'; import { DeleteTicketUseCase } from '../../application/useCases/delete/delete.usecase'; @@ -28,6 +29,7 @@ import { CreateTicketRequest } from '../dtos/create.dto'; import { EscalateTicketRequest } from '../dtos/escalateTicket.dto'; import { TicketMapper } from '../mappers/ticket.mapper'; import { CloseTicketRequest } from '../dtos/closeTicket.dto'; +import { UpdateTicketStatusRequest } from '../dtos/updateTicketStatus.dto'; import { ApiBearerAuth, ApiBody, @@ -210,4 +212,46 @@ export class TicketController { return response; } + + @Put(':id/status') + @ApiOperation({ summary: 'Altera o status de um ticket de forma genérica e integrada' }) + @ApiParam({ name: 'id', example: 'uuid-do-ticket' }) + @ApiBody({ type: UpdateTicketStatusRequest }) + @UseGuards(JwtGuard, RolesGuard) + @Roles(UserRole.ADMIN, UserRole.SUPPORT) + @ApiResponse({ status: 200, description: 'Status do ticket alterado com sucesso.' }) + async updateStatus( + @Request() req: any, + @Param('id') id: string, + @Body() body: UpdateTicketStatusRequest, + ) { + if (body.status === TicketStatus.IN_PROGRESS) { + const data = TicketMapper.toNewAgentInput(id, req.user.id); + return await this.newAgentUseCase.execute(data); + } + + if (body.status === TicketStatus.ESCALATED) { + if (!body.groupId) { + throw new BadRequestException('Escalonamento exige groupId.'); + } + const data = TicketMapper.toEscalateTicketInput(id, { + groupId: body.groupId, + category: body.category || '', + whatWasDone: body.whatWasDone || '', + }); + return await this.escalateUseCase.execute(data); + } + + if (body.status === TicketStatus.CLOSED) { + if (!body.solution) { + throw new BadRequestException('Fechamento do chamado exige uma solução descrita.'); + } + const data = TicketMapper.toCloseTicketInput(id, { + solution: body.solution, + }); + return await this.closeUseCase.execute(data); + } + + throw new BadRequestException('Transição de status inválida ou não suportada.'); + } } diff --git a/backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts b/backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts new file mode 100644 index 0000000..49b6980 --- /dev/null +++ b/backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts @@ -0,0 +1,45 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty, IsEnum, IsOptional } from 'class-validator'; +import { TicketStatus } from '../../domain/entities/ticket.entity'; + +export class UpdateTicketStatusRequest { + @ApiProperty({ + example: 'IN_PROGRESS', + enum: TicketStatus, + }) + @IsEnum(TicketStatus) + @IsNotEmpty() + status: TicketStatus; + + @ApiProperty({ + example: 'O problema foi resolvido reiniciando o servidor.', + required: false, + }) + @IsString() + @IsOptional() + solution?: string; + + @ApiProperty({ + example: 'uuid-do-grupo', + required: false, + }) + @IsString() + @IsOptional() + groupId?: string; + + @ApiProperty({ + example: 'web_app', + required: false, + }) + @IsString() + @IsOptional() + category?: string; + + @ApiProperty({ + example: 'Foi feita a verificação física...', + required: false, + }) + @IsString() + @IsOptional() + whatWasDone?: string; +} From f4aca0c6e4014bc0ff2eb0315fe69f42ccf32af9 Mon Sep 17 00:00:00 2001 From: DaviMBDev Date: Sun, 24 May 2026 17:06:05 -0300 Subject: [PATCH 2/2] fix:passar o pipeline --- .../chat/application/chat.file-message.spec.ts | 12 ++++++++++-- .../modules/chat/application/chat.service.spec.ts | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/backend/src/modules/chat/application/chat.file-message.spec.ts b/backend/src/modules/chat/application/chat.file-message.spec.ts index 422fc42..c86a50e 100644 --- a/backend/src/modules/chat/application/chat.file-message.spec.ts +++ b/backend/src/modules/chat/application/chat.file-message.spec.ts @@ -35,6 +35,14 @@ const mockChat = { createdAt: new Date(), }; +const mockTicketQuery = { + exec: jest.fn().mockResolvedValue(null), +}; + +const mockTicketModel = { + findById: jest.fn().mockReturnValue(mockTicketQuery), +}; + describe('ChatService — envio de mensagem com arquivo', () => { let service: ChatService; @@ -44,8 +52,8 @@ describe('ChatService — envio de mensagem com arquivo', () => { ChatService, { provide: 'IChatRepository', useValue: mockChatRepository }, { provide: 'IMessageRepository', useValue: mockMessageRepository }, - { provide: getModelToken(TicketSchemaClass.name), useValue: {} }, - { provide: getModelToken(UserRole.ADMIN ? 'User' : 'User'), useValue: {} }, + { provide: getModelToken(TicketSchemaClass.name), useValue: mockTicketModel }, + { provide: getModelToken('User'), useValue: {} }, ], }).compile(); diff --git a/backend/src/modules/chat/application/chat.service.spec.ts b/backend/src/modules/chat/application/chat.service.spec.ts index ddd510a..2de2514 100644 --- a/backend/src/modules/chat/application/chat.service.spec.ts +++ b/backend/src/modules/chat/application/chat.service.spec.ts @@ -39,6 +39,14 @@ const mockChat: ChatDetails = { createdAt: new Date('2026-01-01'), }; +const mockTicketQuery = { + exec: jest.fn().mockResolvedValue(null), +}; + +const mockTicketModel = { + findById: jest.fn().mockReturnValue(mockTicketQuery), +}; + describe('ChatService', () => { let service: ChatService; @@ -48,8 +56,8 @@ describe('ChatService', () => { ChatService, { provide: 'IChatRepository', useValue: mockChatRepository }, { provide: 'IMessageRepository', useValue: mockMessageRepository }, - { provide: getModelToken(TicketSchemaClass.name), useValue: {} }, - { provide: getModelToken(UserRole.ADMIN ? 'User' : 'User'), useValue: {} }, // Gambiarra provisória pra obter a string 'User' + { provide: getModelToken(TicketSchemaClass.name), useValue: mockTicketModel }, + { provide: getModelToken('User'), useValue: {} }, ], }).compile();