From f7efda9aab7dfcee5eb41a9fb69283216c6ebc64 Mon Sep 17 00:00:00 2001 From: Humberto Date: Mon, 25 May 2026 20:17:44 -0300 Subject: [PATCH 1/2] =?UTF-8?q?refactor(ticket):=20Ajustar=20n=C3=ADvel=20?= =?UTF-8?q?de=20escalonamento?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Atualiza a rota de escalonamento para permitir alteração do nível de escalonamento do ticket. --- .../useCases/escalate/escalate.usecase.ts | 3 ++- .../ticket/domain/entities/ticket.entity.ts | 14 ++++++-------- .../presentation/controllers/ticket.controller.ts | 1 + .../ticket/presentation/dtos/escalateTicket.dto.ts | 11 ++++++++++- .../presentation/dtos/updateTicketStatus.dto.ts | 13 +++++++++++-- .../ticket/presentation/mappers/ticket.mapper.ts | 1 + 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/backend/src/modules/ticket/application/useCases/escalate/escalate.usecase.ts b/backend/src/modules/ticket/application/useCases/escalate/escalate.usecase.ts index 3cddc24..48bf080 100644 --- a/backend/src/modules/ticket/application/useCases/escalate/escalate.usecase.ts +++ b/backend/src/modules/ticket/application/useCases/escalate/escalate.usecase.ts @@ -9,6 +9,7 @@ import { ChatService } from '../../../../chat/application/chat.service'; export interface EscalateTicketInput { id: string; groupId: string; + escalationLevel?: number; category: string; whatWasDone: string; } @@ -41,7 +42,7 @@ export class EscalateTicketUseCase { throw new Error('Ticket not found'); } - foundedTicket.escalate(input.groupId, input.category, input.whatWasDone); + foundedTicket.escalate(input.groupId, input.escalationLevel, input.category, input.whatWasDone); const escalatedTicket = await this.repository.save(foundedTicket); diff --git a/backend/src/modules/ticket/domain/entities/ticket.entity.ts b/backend/src/modules/ticket/domain/entities/ticket.entity.ts index bdc7e78..bbd33a8 100644 --- a/backend/src/modules/ticket/domain/entities/ticket.entity.ts +++ b/backend/src/modules/ticket/domain/entities/ticket.entity.ts @@ -224,7 +224,7 @@ export class Ticket { } // Escalates the ticket to a new responsible group and optionally changes the category - escalate(groupId: string, category?: string, whatWasDone?: string): void { + escalate(groupId: string, escalationLevel?: number, category?: string, whatWasDone?: string): void { if (!this._agentId) { throw new Error(TicketValidationErrors.ECALATE_WITH_NO_AGENT_ERROR); } @@ -233,14 +233,12 @@ export class Ticket { this._groupId = groupId; - if (category && category !== this.category) { - this.category = category; - this.escalationLevel = 1; - } else { - if (this.escalationLevel >= 3) { - throw new Error(TicketValidationErrors.ESCALATION_LEVEL_MAX_ERROR); + if (escalationLevel !== undefined) { + if (![1, 2, 3].includes(escalationLevel)) { + throw new Error('Invalid escalation level'); } - this.escalationLevel++; + + this.escalationLevel = escalationLevel; } const previousAgentId = this._agentId; diff --git a/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts b/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts index bfbb27d..9b4ed19 100644 --- a/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts +++ b/backend/src/modules/ticket/presentation/controllers/ticket.controller.ts @@ -236,6 +236,7 @@ export class TicketController { } const data = TicketMapper.toEscalateTicketInput(id, { groupId: body.groupId, + escalationLevel: body.escalationLevel, category: body.category || '', whatWasDone: body.whatWasDone || '', }); diff --git a/backend/src/modules/ticket/presentation/dtos/escalateTicket.dto.ts b/backend/src/modules/ticket/presentation/dtos/escalateTicket.dto.ts index b242ae1..0363f6a 100644 --- a/backend/src/modules/ticket/presentation/dtos/escalateTicket.dto.ts +++ b/backend/src/modules/ticket/presentation/dtos/escalateTicket.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsString, IsNumber, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { randomUUID } from 'crypto'; @@ -8,6 +8,15 @@ export class EscalateTicketRequest { @IsNotEmpty() groupId!: string; + @ApiProperty({ + example: 2, + description: 'Nível de escalonamento', + required: false, + }) + @IsNumber() + @IsOptional() + escalationLevel?: number; + @ApiProperty({ example: 'web_app', description: 'Categoria do ticket' }) @IsString() @IsNotEmpty() diff --git a/backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts b/backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts index 49b6980..d7ea1f1 100644 --- a/backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts +++ b/backend/src/modules/ticket/presentation/dtos/updateTicketStatus.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsString, IsNotEmpty, IsEnum, IsOptional } from 'class-validator'; +import { IsString, IsNotEmpty, IsEnum, IsOptional, IsNumber } from 'class-validator'; import { TicketStatus } from '../../domain/entities/ticket.entity'; export class UpdateTicketStatusRequest { @@ -42,4 +42,13 @@ export class UpdateTicketStatusRequest { @IsString() @IsOptional() whatWasDone?: string; -} + + @ApiProperty({ + example: 2, + required: false, + description: 'Nível do escalonamento', + }) + @IsNumber() + @IsOptional() + escalationLevel?: number; +} \ No newline at end of file diff --git a/backend/src/modules/ticket/presentation/mappers/ticket.mapper.ts b/backend/src/modules/ticket/presentation/mappers/ticket.mapper.ts index 684efe3..bb86f6c 100644 --- a/backend/src/modules/ticket/presentation/mappers/ticket.mapper.ts +++ b/backend/src/modules/ticket/presentation/mappers/ticket.mapper.ts @@ -28,6 +28,7 @@ export class TicketMapper { return { id: id, groupId: req.groupId, + escalationLevel: req.escalationLevel, category: req.category, whatWasDone: req.whatWasDone, }; From c92dee78f8f32bf211dfcc6e38b955f6e9d0b0d5 Mon Sep 17 00:00:00 2001 From: Humberto Date: Mon, 25 May 2026 22:39:16 -0300 Subject: [PATCH 2/2] fix(ticket): Corrige escalate e testes de controller e entity --- .../domain/entities/tickect.entity.spec.ts | 25 ++++++++++++++++--- .../ticket/domain/entities/ticket.entity.ts | 16 +++++++++++- .../controllers/ticket.controller.spec.ts | 3 ++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/backend/src/modules/ticket/domain/entities/tickect.entity.spec.ts b/backend/src/modules/ticket/domain/entities/tickect.entity.spec.ts index 224ec5f..8835c59 100644 --- a/backend/src/modules/ticket/domain/entities/tickect.entity.spec.ts +++ b/backend/src/modules/ticket/domain/entities/tickect.entity.spec.ts @@ -69,10 +69,12 @@ describe('Ticket entity', () => { ticket.assignToAgent(newAgentId); const newGroupId = randomUUID(); - ticket.escalate(newGroupId, 'ia'); + + ticket.escalate(newGroupId, 1, 'ia'); expect(ticket.status).toBe(TicketStatus.ESCALATED); expect(ticket.history.length).toBe(3); + expect(ticket.history[2]).toMatchObject({ event: TicketEvents.ESCALATE, responsibleAgent: newAgentId, @@ -84,18 +86,35 @@ describe('Ticket entity', () => { expect(primitiveTicket.groupId).toBe(newGroupId); expect(primitiveTicket.category).toBe('ia'); + + // categoria alterada => nível volta para 1 expect(primitiveTicket.escalationLevel).toBe(1); + expect(primitiveTicket.agentId).toBeNull(); expect(primitiveTicket.updatedAt).toBeInstanceOf(Date); expect(primitiveTicket.updatedAt).not.toBeNull(); }); it('Should throw an error when escalating a ticket without a responsible agent', () => { - expect(() => ticket.escalate(randomUUID(), 'iot')).toThrow( + expect(() => + ticket.escalate(randomUUID(), 1, 'iot'), + ).toThrow( TicketValidationErrors.ECALATE_WITH_NO_AGENT_ERROR, ); }); + it('Should throw an error when closing a ticket with ESCALATED status', () => { + ticket.assignToAgent(randomUUID()); + + ticket.escalate(randomUUID(), 1, 'web_app'); + + expect(ticket.status).toBe(TicketStatus.ESCALATED); + + expect(() => ticket.close('Test solution')).toThrow( + TicketValidationErrors.CLOSE_WITH_WRONG_STATUS_ERROR, + ); + }); + it('Close a ticket should change the status and closedAt field, and add the event to history', () => { ticket.assignToAgent(randomUUID()); ticket.close('Solução exemplo...'); @@ -123,7 +142,7 @@ describe('Ticket entity', () => { it('Should throw an error when closing a ticket with ESCALATED status', () => { ticket.assignToAgent(randomUUID()); - ticket.escalate(randomUUID(), 'web_app'); + ticket.escalate(randomUUID(),1, 'web_app'); expect(ticket.status).toBe(TicketStatus.ESCALATED); expect(() => ticket.close('Test solution')).toThrow( diff --git a/backend/src/modules/ticket/domain/entities/ticket.entity.ts b/backend/src/modules/ticket/domain/entities/ticket.entity.ts index bbd33a8..6686424 100644 --- a/backend/src/modules/ticket/domain/entities/ticket.entity.ts +++ b/backend/src/modules/ticket/domain/entities/ticket.entity.ts @@ -224,7 +224,13 @@ export class Ticket { } // Escalates the ticket to a new responsible group and optionally changes the category - escalate(groupId: string, escalationLevel?: number, category?: string, whatWasDone?: string): void { + escalate( + groupId: string, + escalationLevel?: number, + category?: string, + whatWasDone?: string, + ): void { + if (!this._agentId) { throw new Error(TicketValidationErrors.ECALATE_WITH_NO_AGENT_ERROR); } @@ -233,7 +239,14 @@ export class Ticket { this._groupId = groupId; + // ALTERA A CATEGORIA + if (category) { + this.category = category; + } + + // ALTERA O NÍVEL if (escalationLevel !== undefined) { + if (![1, 2, 3].includes(escalationLevel)) { throw new Error('Invalid escalation level'); } @@ -242,6 +255,7 @@ export class Ticket { } const previousAgentId = this._agentId; + this._agentId = null; this._status = TicketStatus.ESCALATED; diff --git a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts index 93279ca..d87a605 100644 --- a/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts +++ b/backend/src/modules/ticket/presentation/controllers/ticket.controller.spec.ts @@ -306,6 +306,7 @@ describe('TicketController', () => { const payload = { groupUD: randomUUID(), + escalationLevel: 1, category: 'iot', }; @@ -331,7 +332,7 @@ describe('TicketController', () => { const groupId = randomUUID(); ticket.assignToAgent(agentId); - ticket.escalate(groupId, 'iot'); + ticket.escalate(groupId,1, 'iot'); const primitives = ticket.toPrimitives();