Files
SLACK_COORD_RELATORIO/relatorio/Resumo Diario GLPI - SQL Slack.n8n.json
Gustavo Santos d21c4596e1 chore: import relatorio (subtree)
git-subtree-dir: relatorio
git-subtree-mainline: e322cec92b
git-subtree-split: 34bcb132f3
2026-10-01 14:26:02 -03:00

156 lines
12 KiB
JSON

{
"name": "Resumo GLPI - SQL Slack",
"nodes": [
{
"parameters": {},
"id": "manual-trigger-diario-sql",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [180, 420]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
},
"id": "schedule-trigger-glpi-sql",
"name": "Schedule Trigger - Dias uteis 08h",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [180, 180]
},
{
"parameters": {
"assignments": {
"assignments": [
{"id": "data-alvo", "name": "data_alvo", "type": "string", "value": ""},
{"id": "slack-webhook-url", "name": "slack_webhook_url", "type": "string", "value": "https://hooks.slack.com/services/SEU/WEBHOOK/AQUI"},
{"id": "openai-model", "name": "openai_model", "type": "string", "value": "gpt-5.5"}
]
},
"options": {}
},
"id": "config-diario-sql",
"name": "Config - Diario SQL",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [440, 300]
},
{
"parameters": {
"jsCode": "function formatarData(data) {\n const ano = data.getFullYear();\n const mes = String(data.getMonth() + 1).padStart(2, '0');\n const dia = String(data.getDate()).padStart(2, '0');\n return `${ano}-${mes}-${dia}`;\n}\n\nfunction parseData(valor) {\n const texto = String(valor || '').trim();\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(texto)) {\n const [ano, mes, dia] = texto.split('-').map(Number);\n return new Date(ano, mes - 1, dia);\n }\n\n const matchPtBr = texto.match(/^(\\d{2})\\/(\\d{2})\\/(\\d{2}|\\d{4})$/);\n if (matchPtBr) {\n const [, dia, mes, ano] = matchPtBr;\n const anoCompleto = Number(ano.length === 2 ? `20${ano}` : ano);\n return new Date(anoCompleto, Number(mes) - 1, Number(dia));\n }\n\n return null;\n}\n\nfunction adicionarDias(data, dias) {\n const copia = new Date(data);\n copia.setDate(copia.getDate() + dias);\n return copia;\n}\n\nconst config = $('Config - Diario SQL').first().json;\nconst dataManual = parseData(config.data_alvo);\nconst hoje = new Date();\n\nlet inicio;\nlet fim;\nlet tipoPeriodo;\nlet periodoLabel;\n\nif (dataManual) {\n inicio = dataManual;\n fim = dataManual;\n tipoPeriodo = 'manual_diario';\n periodoLabel = formatarData(inicio);\n} else if (hoje.getDay() === 1) {\n inicio = adicionarDias(hoje, -7);\n fim = adicionarDias(hoje, -1);\n tipoPeriodo = 'semanal';\n periodoLabel = `${formatarData(inicio)} a ${formatarData(fim)}`;\n} else {\n inicio = adicionarDias(hoje, -1);\n fim = adicionarDias(hoje, -1);\n tipoPeriodo = 'diario';\n periodoLabel = formatarData(inicio);\n}\n\nconst dataInicio = formatarData(inicio);\nconst dataFim = formatarData(fim);\n\nconst filtroCategorias = `\n SELECT id FROM glpi_itilcategories\n WHERE JSON_CONTAINS(ancestors_cache, '{\"7\":7}') OR id = 7\n OR JSON_CONTAINS(ancestors_cache, '{\"16\":16}') OR id = 16\n OR JSON_CONTAINS(ancestors_cache, '{\"17\":17}') OR id = 17\n OR JSON_CONTAINS(ancestors_cache, '{\"18\":18}') OR id = 18\n`;\n\nconst filtroAbertura = `date >= '${dataInicio}' AND date < DATE_ADD('${dataFim}', INTERVAL 1 DAY)`;\nconst filtroFechamento = `closedate IS NOT NULL AND closedate >= '${dataInicio}' AND closedate < DATE_ADD('${dataFim}', INTERVAL 1 DAY)`;\nconst filtroMovimentoTicket = `((t.date >= '${dataInicio}' AND t.date < DATE_ADD('${dataFim}', INTERVAL 1 DAY)) OR (t.closedate IS NOT NULL AND t.closedate >= '${dataInicio}' AND t.closedate < DATE_ADD('${dataFim}', INTERVAL 1 DAY)))`;\n\nconst query = `\nSELECT\n '${tipoPeriodo}' AS tipo_periodo,\n '${periodoLabel}' AS periodo_label,\n '${dataInicio}' AS data_inicio,\n '${dataFim}' AS data_fim,\n COALESCE(abertos.abertos, 0) AS abertos,\n COALESCE(fechados.fechados, 0) AS fechados,\n COALESCE(fechados.fechados, 0) - COALESCE(abertos.abertos, 0) AS saldo,\n COALESCE(pendentes.pendentes, 0) AS pendentes,\n COALESCE(alta_prioridade.alta_prioridade_pendentes, 0) AS alta_prioridade_pendentes,\n COALESCE(categorias.categorias, 'Sem registros') AS categorias_recorrentes,\n COALESCE(clientes.clientes, 'Sem registros') AS clientes_volume\nFROM\n (SELECT COUNT(*) AS abertos FROM glpi_tickets WHERE is_deleted = false AND ${filtroAbertura} AND itilcategories_id IN (${filtroCategorias})) abertos\nCROSS JOIN\n (SELECT COUNT(*) AS fechados FROM glpi_tickets WHERE is_deleted = false AND ${filtroFechamento} AND itilcategories_id IN (${filtroCategorias})) fechados\nCROSS JOIN\n (SELECT COUNT(*) AS pendentes FROM glpi_tickets WHERE is_deleted = false AND status NOT IN (5, 6) AND itilcategories_id IN (${filtroCategorias})) pendentes\nCROSS JOIN\n (SELECT COUNT(*) AS alta_prioridade_pendentes FROM glpi_tickets WHERE is_deleted = false AND status NOT IN (5, 6) AND priority >= 4 AND itilcategories_id IN (${filtroCategorias})) alta_prioridade\nCROSS JOIN\n (SELECT COALESCE(GROUP_CONCAT(CONCAT(nome, ': ', total) ORDER BY total DESC SEPARATOR '\\n'), 'Sem registros') AS categorias FROM (SELECT COALESCE(c.completename, c.name, 'Nao informado') AS nome, COUNT(*) AS total FROM glpi_tickets t LEFT JOIN glpi_itilcategories c ON c.id = t.itilcategories_id WHERE t.is_deleted = false AND ${filtroMovimentoTicket} AND t.itilcategories_id IN (${filtroCategorias}) GROUP BY t.itilcategories_id, c.completename, c.name ORDER BY total DESC LIMIT 5) ranking_categorias) categorias\nCROSS JOIN\n (SELECT COALESCE(GROUP_CONCAT(CONCAT(nome, ': ', total, ' chamados') ORDER BY total DESC SEPARATOR '\\n'), 'Sem registros') AS clientes FROM (SELECT COALESCE(e.completename, e.name, 'Nao informado') AS nome, COUNT(*) AS total FROM glpi_tickets t LEFT JOIN glpi_entities e ON e.id = t.entities_id WHERE t.is_deleted = false AND ${filtroMovimentoTicket} AND t.itilcategories_id IN (${filtroCategorias}) GROUP BY t.entities_id, e.completename, e.name ORDER BY total DESC LIMIT 5) ranking_clientes) clientes;`;\n\nreturn [{ json: { ...config, tipo_periodo: tipoPeriodo, periodo_label: periodoLabel, data_inicio: dataInicio, data_fim: dataFim, query } }];"
},
"id": "code-montar-sql-diario",
"name": "Code - Montar SQL diario",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [700, 300]
},
{
"parameters": {
"operation": "executeQuery",
"query": "={{ $json.query }}",
"options": {}
},
"id": "mysql-diario-glpi",
"name": "MySQL - Indicadores diario GLPI",
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [960, 300],
"credentials": {"mySql": {"id": "", "name": "MySQL GLPI"}}
},
{
"parameters": {
"jsCode": "const row = $input.first().json;\nconst ehSemanal = row.tipo_periodo === 'semanal';\nconst titulo = ehSemanal ? '*Resumo semanal do suporte GLPI*' : '*Resumo diario do suporte GLPI*';\nconst escopo = ehSemanal ? 'Periodo do relatorio' : 'Data do relatorio';\nconst unidade = ehSemanal ? 'no periodo' : 'no dia';\n\nconst textoBase = [\n titulo,\n '',\n `${escopo}: ${row.periodo_label}`,\n '',\n '*Resumo geral*',\n `- Chamados abertos ${unidade}: ${row.abertos}`,\n `- Chamados fechados ${unidade}: ${row.fechados}`,\n `- Pendentes atuais: ${row.pendentes}`,\n `- Alta prioridade ainda pendentes: ${row.alta_prioridade_pendentes}`,\n `- Saldo fechados - abertos: ${row.saldo}`,\n '',\n '*Categorias mais recorrentes*',\n String(row.categorias_recorrentes || 'Sem registros').split('\\n').map(l => `- ${l}`).join('\\n'),\n '',\n '*Clientes com maior volume*',\n String(row.clientes_volume || 'Sem registros').split('\\n').map(l => `- ${l}`).join('\\n'),\n '',\n '*Observacao*',\n `- Chamados fechados ${unidade} podem ter sido abertos em datas anteriores.`\n].join('\\n');\n\nreturn [{ json: { ...row, texto_base: textoBase } }];"
},
"id": "code-preparar-diario",
"name": "Code - Preparar indicadores",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1220, 300]
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.texto_base }}\n\nDados estruturados para analise:\n{{ JSON.stringify($json) }}",
"options": {
"systemMessage": "Voce e um agente coordenador de relatorios GLPI para Slack. Melhore o texto recebido sem inventar numeros. Destaque abertos, fechados, saldo, pendentes, alta prioridade, categorias e clientes. Se for semanal, trate como periodo semanal. Se for diario, trate como dia anterior. Explique de forma curta que chamados fechados no periodo podem ter sido abertos em datas anteriores. Responda apenas com a mensagem final para Slack em portugues do Brasil."
}
},
"id": "ai-agent-coordenador-glpi",
"name": "AI Agent - Coordenador GLPI",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.7,
"position": [1480, 300],
"continueOnFail": true
},
{
"parameters": {
"model": {
"__rl": true,
"value": "={{ $('Config - Diario SQL').first().json.openai_model || 'gpt-5.5' }}",
"mode": "list",
"cachedResultName": "gpt-5.5"
},
"options": {}
},
"id": "z-ia-chat-model",
"name": "Z IA Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [1480, 520],
"credentials": {"openAiApi": {"id": "", "name": "Z IA OpenAI"}}
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nconst textoIa = data.output || data.text || data.response;\nconst fallback = $('Code - Preparar indicadores').first().json.texto_base;\n\nreturn [{ json: { text: textoIa || fallback } }];"
},
"id": "code-slack-diario",
"name": "Code - Preparar mensagem Slack",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [1740, 300]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Config - Diario SQL').first().json.slack_webhook_url }}",
"sendBody": true,
"contentType": "json",
"specifyBody": "keypair",
"bodyParameters": {"parameters": [{"name": "text", "value": "={{ $json.text }}"}]},
"options": {}
},
"id": "http-slack-diario-sql",
"name": "HTTP - Slack enviar mensagem",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [2000, 300]
}
],
"pinData": {},
"connections": {
"Manual Trigger": {"main": [[{"node": "Config - Diario SQL", "type": "main", "index": 0}]]},
"Schedule Trigger - Dias uteis 08h": {"main": [[{"node": "Config - Diario SQL", "type": "main", "index": 0}]]},
"Config - Diario SQL": {"main": [[{"node": "Code - Montar SQL diario", "type": "main", "index": 0}]]},
"Code - Montar SQL diario": {"main": [[{"node": "MySQL - Indicadores diario GLPI", "type": "main", "index": 0}]]},
"MySQL - Indicadores diario GLPI": {"main": [[{"node": "Code - Preparar indicadores", "type": "main", "index": 0}]]},
"Code - Preparar indicadores": {"main": [[{"node": "AI Agent - Coordenador GLPI", "type": "main", "index": 0}]]},
"Z IA Chat Model": {"ai_languageModel": [[{"node": "AI Agent - Coordenador GLPI", "type": "ai_languageModel", "index": 0}]]},
"AI Agent - Coordenador GLPI": {"main": [[{"node": "Code - Preparar mensagem Slack", "type": "main", "index": 0}]]},
"Code - Preparar mensagem Slack": {"main": [[{"node": "HTTP - Slack enviar mensagem", "type": "main", "index": 0}]]}
},
"active": false,
"settings": {"executionOrder": "v1"},
"versionId": "",
"meta": {"templateCredsSetupCompleted": false},
"tags": []
}