diff --git a/apps/api/src/queues/dto/create-queue.dto.ts b/apps/api/src/queues/dto/create-queue.dto.ts
index d078ba0..4df2bc1 100644
--- a/apps/api/src/queues/dto/create-queue.dto.ts
+++ b/apps/api/src/queues/dto/create-queue.dto.ts
@@ -86,3 +86,80 @@ export class CreateQueueDto {
@IsBoolean()
recordingEnabled?: boolean;
}
+
+export class UpdateQueueDto {
+ @IsOptional()
+ @IsString()
+ @MaxLength(80)
+ name?: string;
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ description?: string;
+
+ @IsOptional()
+ @IsIn(STRATEGIES)
+ strategy?: (typeof STRATEGIES)[number];
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ mohSound?: string;
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(255)
+ announceSound?: string;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ announceFrequency?: number;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ maxWaitTime?: number;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ maxWaitTimeWithNoAgent?: number;
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(80)
+ agentNoAnswerStatus?: string;
+
+ @IsOptional()
+ @IsBoolean()
+ tierRulesApply?: boolean;
+
+ @IsOptional()
+ @IsInt()
+ @Min(1)
+ @Max(3600)
+ tierRuleWaitSecond?: number;
+
+ @IsOptional()
+ @IsInt()
+ @Min(0)
+ @Max(3600)
+ discardAbandonedAfter?: number;
+
+ @IsOptional()
+ @IsBoolean()
+ abandonedResumeAllowed?: boolean;
+
+ @IsOptional()
+ @IsBoolean()
+ skipAgentsWithExternalCalls?: boolean;
+
+ @IsOptional()
+ @IsBoolean()
+ recordingEnabled?: boolean;
+}
diff --git a/apps/api/src/queues/queues.controller.ts b/apps/api/src/queues/queues.controller.ts
index 62551f9..57adc16 100644
--- a/apps/api/src/queues/queues.controller.ts
+++ b/apps/api/src/queues/queues.controller.ts
@@ -7,6 +7,7 @@ import {
HttpStatus,
NotFoundException,
Param,
+ Patch,
Post,
UseGuards,
} from "@nestjs/common";
@@ -18,7 +19,7 @@ import { PermissionGuard } from "../common/guards/permission.guard";
import { RequirePermission } from "../common/decorators/require-permission.decorator";
import { CurrentUser } from "../common/decorators/current-user.decorator";
import { getRedisClient } from "../common/redis";
-import { CreateQueueDto } from "./dto/create-queue.dto";
+import { CreateQueueDto, UpdateQueueDto } from "./dto/create-queue.dto";
const QUEUES_SYNC_CHANNEL = "b2bcall:queues:sync";
@@ -101,6 +102,58 @@ export class QueuesController {
return queue;
}
+ /**
+ * Achado real reportado pelo usuário: "na fila nao tem opcao de editar
+ * a fila apos a criacao" — só existia create/delete até aqui.
+ */
+ @RequirePermission("queues.manage")
+ @Patch(":id")
+ async update(@CurrentUser() user: AccessTokenClaims, @Param("id") id: string, @Body() dto: UpdateQueueDto) {
+ const prisma = getPrismaClient();
+ const tenantId = user.tenantId!;
+
+ const result = await withTenantContext(prisma, tenantId, (tx) =>
+ tx.queue.updateMany({
+ where: { id, tenantId, deletedAt: null },
+ data: {
+ ...(dto.name !== undefined ? { name: dto.name } : {}),
+ ...(dto.description !== undefined ? { description: dto.description } : {}),
+ ...(dto.strategy !== undefined ? { strategy: dto.strategy } : {}),
+ ...(dto.mohSound !== undefined ? { mohSound: dto.mohSound } : {}),
+ ...(dto.announceSound !== undefined ? { announceSound: dto.announceSound } : {}),
+ ...(dto.announceFrequency !== undefined ? { announceFrequency: dto.announceFrequency } : {}),
+ ...(dto.maxWaitTime !== undefined ? { maxWaitTime: dto.maxWaitTime } : {}),
+ ...(dto.maxWaitTimeWithNoAgent !== undefined ? { maxWaitTimeWithNoAgent: dto.maxWaitTimeWithNoAgent } : {}),
+ ...(dto.agentNoAnswerStatus !== undefined ? { agentNoAnswerStatus: dto.agentNoAnswerStatus } : {}),
+ ...(dto.tierRulesApply !== undefined ? { tierRulesApply: dto.tierRulesApply } : {}),
+ ...(dto.tierRuleWaitSecond !== undefined ? { tierRuleWaitSecond: dto.tierRuleWaitSecond } : {}),
+ ...(dto.discardAbandonedAfter !== undefined ? { discardAbandonedAfter: dto.discardAbandonedAfter } : {}),
+ ...(dto.abandonedResumeAllowed !== undefined ? { abandonedResumeAllowed: dto.abandonedResumeAllowed } : {}),
+ ...(dto.skipAgentsWithExternalCalls !== undefined ? { skipAgentsWithExternalCalls: dto.skipAgentsWithExternalCalls } : {}),
+ ...(dto.recordingEnabled !== undefined ? { recordingEnabled: dto.recordingEnabled } : {}),
+ },
+ }),
+ );
+ if (result.count === 0) {
+ throw new NotFoundException();
+ }
+
+ const updated = await withTenantContext(prisma, tenantId, (tx) => tx.queue.findFirstOrThrow({ where: { id } }));
+
+ await recordAuditEvent(prisma, {
+ action: "QUEUE_UPDATE",
+ tenantId,
+ userId: user.sub,
+ entityType: "queue",
+ entityId: id,
+ after: { ...dto },
+ });
+
+ await notifyQueuesChanged();
+
+ return updated;
+ }
+
@RequirePermission("queues.manage")
@Delete(":id")
@HttpCode(HttpStatus.NO_CONTENT)
diff --git a/apps/frontend/src/app/app/callcenter/filas/actions.ts b/apps/frontend/src/app/app/callcenter/filas/actions.ts
index a5698b5..71811f4 100644
--- a/apps/frontend/src/app/app/callcenter/filas/actions.ts
+++ b/apps/frontend/src/app/app/callcenter/filas/actions.ts
@@ -40,6 +40,20 @@ export async function createQueue(input: CreateQueueInput): Promise<{ ok: true;
}
}
+export async function updateQueue(
+ id: string,
+ input: CreateQueueInput,
+): Promise<{ ok: true; queue: Queue } | { ok: false; error: string }> {
+ const session = await requireSession();
+ try {
+ const queue = await apiFetch