omnichannel-backend/src/modules/attendance/repositories/attendance-assignment.repository.ts

478 lines
15 KiB
TypeScript
Raw Normal View History

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,
2026-06-09 18:14:55 -03:00
conversation_started_at = CURRENT_TIMESTAMP,
expires_at = CURRENT_TIMESTAMP + INTERVAL '24 hours',
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,
2026-06-09 18:14:55 -03:00
conversation_started_at = CURRENT_TIMESTAMP,
expires_at = CURRENT_TIMESTAMP + INTERVAL '24 hours',
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<string | number | null> = [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<any>(
`
SELECT *
FROM bot_triage_flows
WHERE active = TRUE
ORDER BY id ASC
LIMIT 1
`,
);
}
listActiveTriageAudiences(flowId: number) {
return this.database.query<any>(
`
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<any>(
`
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<any>(
`
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<any>(
`
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],
);
}
}