import { Injectable } from '@nestjs/common'; import { DatabaseService } from '../../../infra/database/database.service'; @Injectable() export class AttendanceAssignmentRepository { constructor(private readonly database: DatabaseService) {} assignChat(chatId: string, userId: number, areaId?: number | null) { return this.database.query( ` INSERT INTO whatsapp_chat_atribuicoes ( chat_id, user_id, area_id, status, conversation_started_at, expires_at, assigned_at, updated_at ) VALUES ($1, $2, $3, 'assigned', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP + INTERVAL '24 hours', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT (chat_id) DO UPDATE SET user_id = EXCLUDED.user_id, area_id = COALESCE(EXCLUDED.area_id, whatsapp_chat_atribuicoes.area_id), status = 'assigned', awaiting_customer_reply = FALSE, reserved_user_id = NULL, reserved_at = NULL, pause_released_at = NULL, triage_flow_id = NULL, triage_audience_id = NULL, triage_intent_id = NULL, triage_step = NULL, triage_builder_version_id = NULL, triage_builder_node_id = NULL, assigned_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP RETURNING *; `, [chatId, userId, areaId || null], ); } queueChat(chatId: string, areaId: number, note?: string | null) { return this.database.query( ` INSERT INTO whatsapp_chat_atribuicoes ( chat_id, user_id, area_id, status, conversation_started_at, expires_at, transfer_note, assigned_at, updated_at ) VALUES ($1, NULL, $2, 'queued', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP + INTERVAL '24 hours', $3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT (chat_id) DO UPDATE SET user_id = NULL, area_id = EXCLUDED.area_id, status = 'queued', reserved_user_id = NULL, reserved_at = NULL, pause_released_at = NULL, conversation_started_at = CASE WHEN whatsapp_chat_atribuicoes.expires_at <= CURRENT_TIMESTAMP THEN CURRENT_TIMESTAMP ELSE whatsapp_chat_atribuicoes.conversation_started_at END, expires_at = CASE WHEN whatsapp_chat_atribuicoes.expires_at <= CURRENT_TIMESTAMP THEN CURRENT_TIMESTAMP + INTERVAL '24 hours' ELSE whatsapp_chat_atribuicoes.expires_at END, transfer_note = EXCLUDED.transfer_note, triage_flow_id = NULL, triage_audience_id = NULL, triage_intent_id = NULL, triage_step = NULL, triage_builder_version_id = NULL, triage_builder_node_id = NULL, updated_at = CURRENT_TIMESTAMP RETURNING *; `, [chatId, areaId, note || null], ); } transferChat(input: { chatId: string; userId?: number | null; areaId: number; status: string; note?: string | null }) { return this.database.query( ` INSERT INTO whatsapp_chat_atribuicoes ( chat_id, user_id, area_id, status, conversation_started_at, expires_at, transfer_note, assigned_at, updated_at ) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP + INTERVAL '24 hours', $5, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT (chat_id) DO UPDATE SET user_id = EXCLUDED.user_id, area_id = EXCLUDED.area_id, status = EXCLUDED.status, transfer_note = EXCLUDED.transfer_note, reserved_user_id = NULL, reserved_at = NULL, pause_released_at = NULL, triage_flow_id = NULL, triage_audience_id = NULL, triage_intent_id = NULL, triage_step = NULL, triage_builder_version_id = NULL, triage_builder_node_id = NULL, assigned_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP RETURNING *; `, [input.chatId, input.userId || null, input.areaId, input.status, input.note || null], ); } getAssignment(chatId: string) { return this.database.query( ` SELECT * FROM whatsapp_chat_atribuicoes WHERE chat_id = $1 LIMIT 1 `, [chatId], ); } expireAssignment(chatId: string) { return this.database.query( `UPDATE whatsapp_chat_atribuicoes SET status = 'expired', user_id = NULL, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1`, [chatId], ); } releaseChat(chatId: string) { return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET user_id = NULL, status = 'queued', reserved_user_id = NULL, reserved_at = NULL, pause_released_at = NULL, triage_flow_id = NULL, triage_audience_id = NULL, triage_intent_id = NULL, triage_step = NULL, triage_builder_version_id = NULL, triage_builder_node_id = NULL, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 RETURNING *; `, [chatId], ); } closeChat(chatId: string, userId?: string | number | null) { const params: Array = [chatId]; const userGuard = userId ? 'AND (user_id = $2 OR user_id IS NULL)' : ''; if (userId) params.push(Number(userId)); return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET user_id = NULL, area_id = NULL, status = 'expired', awaiting_customer_reply = FALSE, reserved_user_id = NULL, reserved_at = NULL, pause_released_at = NULL, triage_flow_id = NULL, triage_audience_id = NULL, triage_intent_id = NULL, triage_step = NULL, triage_builder_version_id = NULL, triage_builder_node_id = NULL, transfer_note = NULL, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 ${userGuard} RETURNING * `, params, ); } clearTransferNote(chatId: string) { return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET transfer_note = NULL, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 RETURNING * `, [chatId], ); } markAwaitingCustomerReply(chatId: string) { return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET awaiting_customer_reply = TRUE, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 RETURNING * `, [chatId], ); } markCustomerReplied(chatId: string) { return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET awaiting_customer_reply = FALSE, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 AND awaiting_customer_reply = TRUE RETURNING * `, [chatId], ); } getChatsByArea(areaId: string) { return this.database.query(`SELECT * FROM whatsapp_chat_atribuicoes WHERE area_id = $1 AND status <> 'expired'`, [areaId]); } getChatsByUser(userId: string) { return this.database.query(`SELECT * FROM whatsapp_chat_atribuicoes WHERE user_id = $1 AND status = 'assigned'`, [userId]); } closeBotConversation(chatId: string, messageId?: string) { return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET status = 'expired', user_id = NULL, area_id = NULL, triage_flow_id = NULL, triage_audience_id = NULL, triage_intent_id = NULL, triage_step = NULL, triage_builder_version_id = NULL, triage_builder_node_id = NULL, last_routed_message_id = $2, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 `, [chatId, messageId || null], ); } getActiveTriageFlow() { return this.database.query( ` SELECT * FROM bot_triage_flows WHERE active = TRUE ORDER BY id ASC LIMIT 1 `, ); } listActiveTriageAudiences(flowId: number) { return this.database.query( ` SELECT * FROM bot_triage_audiences WHERE flow_id = $1 AND active = TRUE ORDER BY sort_order ASC, id ASC `, [flowId], ); } listActiveTriageIntents(audienceIds: number[]) { return this.database.query( ` SELECT bti.*, a.nome AS area_nome FROM bot_triage_intents bti INNER JOIN areas a ON a.id = bti.area_id WHERE bti.audience_id = ANY($1::int[]) AND bti.active = TRUE ORDER BY bti.sort_order ASC, bti.id ASC `, [audienceIds], ); } getPublishedBotFlowVersion() { return this.database.query( ` SELECT * FROM bot_flow_versions WHERE status = 'published' AND root_node_id IS NOT NULL ORDER BY published_at DESC, id DESC LIMIT 1 `, ); } listBotFlowNodes(versionId: number) { return this.database.query( ` SELECT node.*, area.nome AS area_nome, fallback_area.nome AS fallback_area_nome FROM bot_flow_nodes node LEFT JOIN areas area ON area.id = node.area_id LEFT JOIN areas fallback_area ON fallback_area.id = node.fallback_area_id WHERE node.version_id = $1 ORDER BY node.parent_id NULLS FIRST, node.sort_order ASC, node.id ASC `, [versionId], ); } listRoutingKeywords() { return this.database.query<{ id: number; nome: string; keyword: string }>( ` SELECT a.id, a.nome, ark.keyword FROM area_routing_keywords ark INNER JOIN areas a ON a.id = ark.area_id WHERE ark.active = TRUE AND a.ativo = TRUE ORDER BY LENGTH(ark.keyword) DESC `, ); } getAreaByName(targetName: string) { return this.database.query<{ id: number; nome: string }>( `SELECT id, nome FROM areas WHERE nome = $1 LIMIT 1`, [targetName], ); } upsertTriage(chatId: string, attempts: number, messageId?: string) { return this.database.query( ` INSERT INTO whatsapp_chat_atribuicoes ( chat_id, user_id, area_id, status, routing_attempts, last_routed_message_id, last_bot_sent_at, conversation_started_at, expires_at, assigned_at, updated_at ) VALUES ( $1, NULL, NULL, 'bot_triage', $2, $3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP + INTERVAL '24 hours', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP ) ON CONFLICT (chat_id) DO UPDATE SET user_id = NULL, area_id = NULL, status = 'bot_triage', routing_attempts = EXCLUDED.routing_attempts, last_routed_message_id = EXCLUDED.last_routed_message_id, last_bot_sent_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP RETURNING *; `, [chatId, attempts, messageId || null], ); } upsertConfiguredTriage(input: { chatId: string; flowId: number; audienceId: number | null; step: 'audience' | 'intent' | 'resolution'; attempts: number; messageId?: string; intentId: number | null; }) { return this.database.query( ` INSERT INTO whatsapp_chat_atribuicoes ( chat_id, user_id, area_id, status, routing_attempts, last_routed_message_id, last_bot_sent_at, triage_flow_id, triage_audience_id, triage_intent_id, triage_step, conversation_started_at, expires_at, assigned_at, updated_at ) VALUES ( $1, NULL, NULL, 'bot_triage', $2, $3, CURRENT_TIMESTAMP, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP + INTERVAL '24 hours', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP ) ON CONFLICT (chat_id) DO UPDATE SET user_id = NULL, area_id = NULL, status = 'bot_triage', routing_attempts = EXCLUDED.routing_attempts, last_routed_message_id = EXCLUDED.last_routed_message_id, last_bot_sent_at = CURRENT_TIMESTAMP, triage_flow_id = EXCLUDED.triage_flow_id, triage_audience_id = EXCLUDED.triage_audience_id, triage_intent_id = EXCLUDED.triage_intent_id, triage_step = EXCLUDED.triage_step, updated_at = CURRENT_TIMESTAMP RETURNING *; `, [input.chatId, input.attempts, input.messageId || null, input.flowId, input.audienceId, input.intentId, input.step], ); } upsertBuilderTriage(chatId: string, versionId: number, nodeId: number, attempts: number, messageId?: string) { return this.database.query( ` INSERT INTO whatsapp_chat_atribuicoes ( chat_id, user_id, area_id, status, routing_attempts, last_routed_message_id, last_bot_sent_at, triage_builder_version_id, triage_builder_node_id, conversation_started_at, expires_at, assigned_at, updated_at ) VALUES ( $1, NULL, NULL, 'bot_triage', $2, $3, CURRENT_TIMESTAMP, $4, $5, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP + INTERVAL '24 hours', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP ) ON CONFLICT (chat_id) DO UPDATE SET user_id = NULL, area_id = NULL, status = 'bot_triage', routing_attempts = EXCLUDED.routing_attempts, last_routed_message_id = EXCLUDED.last_routed_message_id, last_bot_sent_at = CURRENT_TIMESTAMP, triage_flow_id = NULL, triage_audience_id = NULL, triage_intent_id = NULL, triage_step = NULL, triage_builder_version_id = EXCLUDED.triage_builder_version_id, triage_builder_node_id = EXCLUDED.triage_builder_node_id, updated_at = CURRENT_TIMESTAMP RETURNING *; `, [chatId, attempts, messageId || null, versionId, nodeId], ); } markBotRoute(chatId: string, messageId?: string, updateBotTimestamp = true) { return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET routing_attempts = 0, last_routed_message_id = $2, last_bot_sent_at = CASE WHEN $3 THEN CURRENT_TIMESTAMP ELSE last_bot_sent_at END, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 `, [chatId, messageId || null, updateBotTimestamp], ); } markMessageRouted(chatId: string, messageId?: string) { return this.database.query( ` UPDATE whatsapp_chat_atribuicoes SET last_routed_message_id = $2, updated_at = CURRENT_TIMESTAMP WHERE chat_id = $1 `, [chatId, messageId || null], ); } enrichAssignment(assignmentId: number) { return this.database.query( ` SELECT wca.*, a.nome AS area_nome, u.nome AS user_nome, u.email AS user_email FROM whatsapp_chat_atribuicoes wca LEFT JOIN areas a ON a.id = wca.area_id LEFT JOIN usuarios u ON u.id = wca.user_id WHERE wca.id = $1 `, [assignmentId], ); } }