diff --git a/main.go b/main.go index 42a745a..4c2f7f1 100644 --- a/main.go +++ b/main.go @@ -845,6 +845,206 @@ func formatChargeabilityForLLM(result []ProfissionalChargeability, args Chargeab return b.String() } +// ── Tool: pesquisar_usuarios / get_usuario ── + +type UsuarioResumo struct { + Nome string `json:"nome"` + Email string `json:"email"` + Cargo string `json:"cargo"` + Ativo string `json:"ativo"` + Disponivel string `json:"disponivel"` +} + +func pesquisarUsuarios(busca string) ([]UsuarioResumo, error) { + token, cookie, err := qualitorLogin() + if err != nil { + return nil, err + } + + safe := safeSQLString(busca) + like := "%" + safe + "%" + + sql := `SELECT u.nmusuario, u.dsemail, u.idativo, u.iddisponivel, c.nmcargo +FROM QUALITOR.dbo.ad_usuario u +LEFT JOIN QUALITOR.dbo.ad_cargo c ON c.cdcargo = u.cdcargo +WHERE (u.nmusuario LIKE '` + like + `' OR u.dsemail LIKE '` + like + `' OR u.nmusuariorede LIKE '` + like + `') + AND u.idativo = 'Y' +ORDER BY u.nmusuario` + + xmlResult, err := qualitorQuery(token, cookie, sql) + if err != nil { + return nil, err + } + + decoded := extractResult(xmlResult) + items := extractDataItems(decoded) + var usuarios []UsuarioResumo + for _, block := range items { + f := extractAllFields(block) + usuarios = append(usuarios, UsuarioResumo{ + Nome: f["nmusuario"], + Email: f["dsemail"], + Cargo: f["nmcargo"], + Ativo: f["idativo"], + Disponivel: f["iddisponivel"], + }) + } + return usuarios, nil +} + +type UsuarioDetalhado struct { + Nome string `json:"nome"` + Email string `json:"email"` + Login string `json:"login"` + Cargo string `json:"cargo"` + Superior string `json:"superior"` + Ativo string `json:"ativo"` + Disponivel string `json:"disponivel"` + Bloqueado string `json:"bloqueado"` + Equipes []string `json:"equipes"` + Total30d string `json:"total_chamados_30d"` + Terminados string `json:"terminados_30d"` + Abertos string `json:"abertos_30d"` +} + +func getUsuario(nome string) (*UsuarioDetalhado, error) { + token, cookie, err := qualitorLogin() + if err != nil { + return nil, err + } + + safe := safeSQLString(nome) + + // Dados do usuario + sqlUser := `SELECT u.nmusuario, u.dsemail, u.nmusuariorede, u.idativo, u.iddisponivel, u.idbloqueado, + c.nmcargo, s.nmusuario AS superior +FROM QUALITOR.dbo.ad_usuario u +LEFT JOIN QUALITOR.dbo.ad_cargo c ON c.cdcargo = u.cdcargo +LEFT JOIN QUALITOR.dbo.ad_usuario s ON s.cdusuario = u.cdusuariosuperior +WHERE u.nmusuario = '` + safe + `'` + + // Equipes + sqlEquipes := `SELECT e.nmequipe +FROM QUALITOR.dbo.hd_equipe e +JOIN QUALITOR.dbo.hd_equipeusuario eu ON eu.cdequipe = e.cdequipe +JOIN QUALITOR.dbo.ad_usuario u ON u.cdusuario = eu.cdusuario +WHERE u.nmusuario = '` + safe + `' +ORDER BY e.nmequipe` + + // Stats 30 dias + sqlStats := `SELECT + COUNT(*) AS total, + SUM(CASE WHEN STATUS = 'TERMINADO' THEN 1 ELSE 0 END) AS terminados, + SUM(CASE WHEN STATUS NOT IN ('TERMINADO','Cancelado') THEN 1 ELSE 0 END) AS abertos +FROM lgit_chamados +WHERE ATENDENTE = '` + safe + `' AND DATA_ABERTURA >= DATEADD(DAY, -30, GETDATE())` + + xmlUser, err := qualitorQuery(token, cookie, sqlUser) + if err != nil { + return nil, err + } + xmlEquipes, err := qualitorQuery(token, cookie, sqlEquipes) + if err != nil { + return nil, err + } + xmlStats, err := qualitorQuery(token, cookie, sqlStats) + if err != nil { + return nil, err + } + + // Parse user + decoded := extractResult(xmlUser) + items := extractDataItems(decoded) + if len(items) == 0 { + return nil, fmt.Errorf("usuário '%s' não encontrado", nome) + } + f := extractAllFields(items[0]) + + result := &UsuarioDetalhado{ + Nome: f["nmusuario"], + Email: f["dsemail"], + Login: f["nmusuariorede"], + Cargo: f["nmcargo"], + Superior: f["superior"], + Ativo: f["idativo"], + Disponivel: f["iddisponivel"], + Bloqueado: f["idbloqueado"], + } + + // Parse equipes + decodedEq := extractResult(xmlEquipes) + eqItems := extractDataItems(decodedEq) + for _, block := range eqItems { + eq := extractField(block, "nmequipe") + if eq != "" { + result.Equipes = append(result.Equipes, eq) + } + } + + // Parse stats + decodedSt := extractResult(xmlStats) + stItems := extractDataItems(decodedSt) + if len(stItems) > 0 { + sf := extractAllFields(stItems[0]) + result.Total30d = sf["total"] + result.Terminados = sf["terminados"] + result.Abertos = sf["abertos"] + } + + return result, nil +} + +func formatUsuarioForLLM(u *UsuarioDetalhado) string { + var b strings.Builder + b.WriteString(fmt.Sprintf("# Usuário: %s\n\n", u.Nome)) + b.WriteString(fmt.Sprintf("**E-mail:** %s\n", u.Email)) + b.WriteString(fmt.Sprintf("**Login:** %s\n", u.Login)) + b.WriteString(fmt.Sprintf("**Cargo:** %s\n", u.Cargo)) + b.WriteString(fmt.Sprintf("**Superior:** %s\n", u.Superior)) + + status := "Ativo" + if u.Ativo != "Y" { + status = "Inativo" + } + if u.Bloqueado == "S" { + status += " (Bloqueado)" + } + b.WriteString(fmt.Sprintf("**Status:** %s | **Disponível:** %s\n", status, u.Disponivel)) + + if len(u.Equipes) > 0 { + b.WriteString(fmt.Sprintf("**Equipes:** %s\n", strings.Join(u.Equipes, ", "))) + } + + b.WriteString(fmt.Sprintf("\n## Chamados (últimos 30 dias)\n")) + b.WriteString(fmt.Sprintf("- Total: %s\n", u.Total30d)) + b.WriteString(fmt.Sprintf("- Terminados: %s\n", u.Terminados)) + b.WriteString(fmt.Sprintf("- Em aberto: %s\n", u.Abertos)) + + return b.String() +} + +func formatPesquisaUsuariosForLLM(usuarios []UsuarioResumo, busca string) string { + var b strings.Builder + b.WriteString(fmt.Sprintf("# Pesquisa de Usuários (%d resultados)\n\n", len(usuarios))) + b.WriteString(fmt.Sprintf("**Busca:** \"%s\"\n\n", busca)) + + if len(usuarios) == 0 { + b.WriteString("Nenhum usuário encontrado.\n") + return b.String() + } + + b.WriteString("| Nome | E-mail | Cargo | Disponível |\n") + b.WriteString("|------|--------|-------|------------|\n") + for _, u := range usuarios { + disp := "Sim" + if u.Disponivel != "Y" { + disp = "Não" + } + b.WriteString(fmt.Sprintf("| %s | %s | %s | %s |\n", u.Nome, u.Email, u.Cargo, disp)) + } + return b.String() +} + func filterEmpty(ss []string) []string { var out []string for _, s := range ss { @@ -990,6 +1190,34 @@ func handleToolsList(id json.RawMessage, w io.Writer) { }, }, }, + { + "name": "pesquisar_usuarios", + "description": "Pesquisa usuários ativos no Qualitor por nome, e-mail ou login de rede. Retorna nome, e-mail, cargo e disponibilidade.", + "inputSchema": map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "busca": map[string]interface{}{ + "type": "string", + "description": "Texto para buscar no nome, e-mail ou login do usuário (ex: 'Odilon', 'odilon.silva', 'grupothink')", + }, + }, + "required": []string{"busca"}, + }, + }, + { + "name": "get_usuario", + "description": "Busca informações detalhadas de um usuário no Qualitor pelo nome completo. Retorna dados pessoais (e-mail, login, cargo, superior hierárquico), equipes que participa, status (ativo/bloqueado/disponível) e estatísticas de chamados dos últimos 30 dias.", + "inputSchema": map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "nome": map[string]interface{}{ + "type": "string", + "description": "Nome completo do usuário no Qualitor (ex: 'Odilon Pascoal Mendes Silva')", + }, + }, + "required": []string{"nome"}, + }, + }, }, }) } @@ -1081,6 +1309,74 @@ func handleToolCall(id json.RawMessage, params json.RawMessage, w io.Writer) { }, }) + case "pesquisar_usuarios": + var args struct { + Busca string `json:"busca"` + } + json.Unmarshal(p.Arguments, &args) + + if args.Busca == "" { + sendResponse(w, id, map[string]interface{}{ + "content": []map[string]interface{}{ + {"type": "text", "text": "Erro: campo 'busca' é obrigatório"}, + }, + "isError": true, + }) + return + } + + usuarios, err := pesquisarUsuarios(args.Busca) + if err != nil { + sendResponse(w, id, map[string]interface{}{ + "content": []map[string]interface{}{ + {"type": "text", "text": fmt.Sprintf("Erro: %s", err.Error())}, + }, + "isError": true, + }) + return + } + + text := formatPesquisaUsuariosForLLM(usuarios, args.Busca) + sendResponse(w, id, map[string]interface{}{ + "content": []map[string]interface{}{ + {"type": "text", "text": text}, + }, + }) + + case "get_usuario": + var args struct { + Nome string `json:"nome"` + } + json.Unmarshal(p.Arguments, &args) + + if args.Nome == "" { + sendResponse(w, id, map[string]interface{}{ + "content": []map[string]interface{}{ + {"type": "text", "text": "Erro: campo 'nome' é obrigatório"}, + }, + "isError": true, + }) + return + } + + usuario, err := getUsuario(args.Nome) + if err != nil { + sendResponse(w, id, map[string]interface{}{ + "content": []map[string]interface{}{ + {"type": "text", "text": fmt.Sprintf("Erro: %s", err.Error())}, + }, + "isError": true, + }) + return + } + + text := formatUsuarioForLLM(usuario) + sendResponse(w, id, map[string]interface{}{ + "content": []map[string]interface{}{ + {"type": "text", "text": text}, + }, + }) + default: sendResponse(w, id, map[string]interface{}{ "content": []map[string]interface{}{ diff --git a/mcp-qualitor b/mcp-qualitor index caaf827..80cf672 100755 Binary files a/mcp-qualitor and b/mcp-qualitor differ