epa-testeprojetoia 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.idea/epa_mcp.iml +8 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/php.xml +19 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/AGENTS.md +10 -0
  6. package/README.md +339 -0
  7. package/dist/agent/agentHelpers.js +21 -0
  8. package/dist/agent/openaiAgent.js +170 -0
  9. package/dist/agent/slidingWindow.js +13 -0
  10. package/dist/api/epaApiClient.js +59 -0
  11. package/dist/cli/index.js +47 -0
  12. package/dist/config/credentialStore.js +92 -0
  13. package/dist/config/ensureCliAuth.js +127 -0
  14. package/dist/config/loadConfig.js +40 -0
  15. package/dist/config/setup.js +23 -0
  16. package/dist/core/createReferenceTool.js +12 -0
  17. package/dist/core/createTool.js +32 -0
  18. package/dist/mocks/requestMocks.js +47 -0
  19. package/dist/server/server.js +41 -0
  20. package/dist/server/stdioServer.js +3 -0
  21. package/dist/services/request/requestFilters.js +1 -0
  22. package/dist/services/requestService.js +81 -0
  23. package/dist/services/teamService.js +18 -0
  24. package/dist/sql/createSqlConnection.js +13 -0
  25. package/dist/sql/fetchSchemaSummary.js +38 -0
  26. package/dist/sql/generateSqlFromQuestion.js +83 -0
  27. package/dist/sql/generateSqlPlan.js +111 -0
  28. package/dist/sql/getSqlAgentErrorMessage.js +15 -0
  29. package/dist/sql/loadSqlAgentConfig.js +24 -0
  30. package/dist/sql/loadSqlConfig.js +43 -0
  31. package/dist/sql/parseSqlQuestionHints.js +29 -0
  32. package/dist/sql/runSqlAgentCli.js +163 -0
  33. package/dist/sql/runSqlCli.js +34 -0
  34. package/dist/sql/selectRelevantTables.js +136 -0
  35. package/dist/sql/sqlGuard.js +21 -0
  36. package/dist/sql/sqlPlan.js +16 -0
  37. package/dist/tests/requestService.test.js +110 -0
  38. package/dist/tools/analytics/teamReport.draft.js +16 -0
  39. package/dist/tools/loadTools.js +40 -0
  40. package/dist/tools/requests/assignees.js +50 -0
  41. package/dist/tools/requests/clients.draft.js +10 -0
  42. package/dist/tools/requests/create.draft.js +51 -0
  43. package/dist/tools/requests/list.js +50 -0
  44. package/dist/tools/requests/priorities.js +2 -0
  45. package/dist/tools/requests/services.draft.js +20 -0
  46. package/dist/tools/requests/types.js +16 -0
  47. package/dist/tools/requests/units.draft.js +10 -0
  48. package/dist/tools/requests/view.draft.js +20 -0
  49. package/dist/utils/buildDateRange.js +18 -0
  50. package/dist/utils/findIdByDescription.js +4 -0
  51. package/dist/utils/resolveAssigneeId.js +14 -0
  52. package/dist/utils/toolNameMaps.js +23 -0
  53. package/package.json +31 -0
  54. package/src/agent/agentHelpers.ts +25 -0
  55. package/src/agent/openaiAgent.ts +205 -0
  56. package/src/agent/slidingWindow.ts +17 -0
  57. package/src/api/epaApiClient.ts +82 -0
  58. package/src/cli/index.ts +61 -0
  59. package/src/config/credentialStore.ts +130 -0
  60. package/src/config/ensureCliAuth.ts +152 -0
  61. package/src/config/loadConfig.ts +62 -0
  62. package/src/config/setup.ts +35 -0
  63. package/src/core/createReferenceTool.ts +17 -0
  64. package/src/core/createTool.ts +51 -0
  65. package/src/mocks/requestMocks.ts +52 -0
  66. package/src/server/server.ts +61 -0
  67. package/src/server/stdioServer.ts +5 -0
  68. package/src/services/request/requestFilters.ts +12 -0
  69. package/src/services/requestService.ts +126 -0
  70. package/src/services/teamService.ts +27 -0
  71. package/src/sql/createSqlConnection.ts +15 -0
  72. package/src/sql/fetchSchemaSummary.ts +64 -0
  73. package/src/sql/generateSqlFromQuestion.ts +105 -0
  74. package/src/sql/generateSqlPlan.ts +133 -0
  75. package/src/sql/getSqlAgentErrorMessage.ts +24 -0
  76. package/src/sql/loadSqlAgentConfig.ts +33 -0
  77. package/src/sql/loadSqlConfig.ts +75 -0
  78. package/src/sql/parseSqlQuestionHints.ts +46 -0
  79. package/src/sql/runSqlAgentCli.ts +204 -0
  80. package/src/sql/runSqlCli.ts +40 -0
  81. package/src/sql/selectRelevantTables.ts +184 -0
  82. package/src/sql/sqlGuard.ts +28 -0
  83. package/src/sql/sqlPlan.ts +28 -0
  84. package/src/tests/requestService.test.ts +152 -0
  85. package/src/tools/analytics/teamReport.draft.ts +25 -0
  86. package/src/tools/loadTools.ts +59 -0
  87. package/src/tools/requests/assignees.ts +59 -0
  88. package/src/tools/requests/clients.draft.ts +18 -0
  89. package/src/tools/requests/create.draft.ts +59 -0
  90. package/src/tools/requests/list.ts +57 -0
  91. package/src/tools/requests/priorities.ts +6 -0
  92. package/src/tools/requests/services.draft.ts +24 -0
  93. package/src/tools/requests/types.ts +18 -0
  94. package/src/tools/requests/units.draft.ts +18 -0
  95. package/src/tools/requests/view.draft.ts +27 -0
  96. package/src/utils/buildDateRange.ts +22 -0
  97. package/src/utils/findIdByDescription.ts +10 -0
  98. package/src/utils/resolveAssigneeId.ts +24 -0
  99. package/src/utils/toolNameMaps.ts +33 -0
  100. package/tsconfig.json +11 -0
@@ -0,0 +1,184 @@
1
+ export type RankedTable = {
2
+ name: string
3
+ score: number
4
+ }
5
+
6
+ type TableSelection = {
7
+ candidateTables: RankedTable[]
8
+ suggestedTables: string[]
9
+ requiresConfirmation: boolean
10
+ }
11
+
12
+ const QUESTION_STOP_WORDS = new Set([
13
+ "a",
14
+ "as",
15
+ "ao",
16
+ "aos",
17
+ "com",
18
+ "da",
19
+ "das",
20
+ "de",
21
+ "do",
22
+ "dos",
23
+ "e",
24
+ "em",
25
+ "na",
26
+ "nas",
27
+ "no",
28
+ "nos",
29
+ "o",
30
+ "os",
31
+ "ou",
32
+ "para",
33
+ "por",
34
+ "quais",
35
+ "qual",
36
+ "quero",
37
+ "traga",
38
+ "trazer",
39
+ "listar",
40
+ "liste",
41
+ "mostrar",
42
+ "mostre",
43
+ "me",
44
+ "uma",
45
+ "um"
46
+ ])
47
+
48
+ function normalizeText(value: string) {
49
+ return value
50
+ .normalize("NFD")
51
+ .replace(/[\u0300-\u036f]/g, "")
52
+ .toLowerCase()
53
+ }
54
+
55
+ function buildTokenVariants(token: string) {
56
+ const variants = new Set([token])
57
+
58
+ if (token.endsWith("es") && token.length > 4) {
59
+ variants.add(token.slice(0, -2))
60
+ }
61
+
62
+ if (token.endsWith("s") && token.length > 3) {
63
+ variants.add(token.slice(0, -1))
64
+ }
65
+
66
+ return [...variants]
67
+ }
68
+
69
+ function extractRelevantTerms(question: string) {
70
+ const normalizedQuestion = normalizeText(question)
71
+ const rawTokens = normalizedQuestion.match(/[a-z0-9_]+/g) ?? []
72
+
73
+ return rawTokens
74
+ .filter((token) => token.length >= 3)
75
+ .filter((token) => !QUESTION_STOP_WORDS.has(token))
76
+ .flatMap(buildTokenVariants)
77
+ }
78
+
79
+ function scoreTableName(tableName: string, terms: string[]) {
80
+ if (terms.length === 0) {
81
+ return 0
82
+ }
83
+
84
+ const normalizedTableName = normalizeText(tableName)
85
+ let score = 0
86
+
87
+ for (const term of terms) {
88
+ if (normalizedTableName === term) {
89
+ score += 100
90
+ continue
91
+ }
92
+
93
+ if (normalizedTableName.startsWith(`${term}_`) || normalizedTableName.endsWith(`_${term}`)) {
94
+ score += 70
95
+ continue
96
+ }
97
+
98
+ if (normalizedTableName.includes(`_${term}_`)) {
99
+ score += 60
100
+ continue
101
+ }
102
+
103
+ if (normalizedTableName.includes(term)) {
104
+ score += 40
105
+ }
106
+ }
107
+
108
+ return score
109
+ }
110
+
111
+ function shouldAskForConfirmation(candidateTables: RankedTable[]) {
112
+ if (candidateTables.length <= 1) {
113
+ return false
114
+ }
115
+
116
+ const [topCandidate, secondCandidate] = candidateTables
117
+
118
+ if (!topCandidate || !secondCandidate) {
119
+ return false
120
+ }
121
+
122
+ if (topCandidate.score === 0) {
123
+ return true
124
+ }
125
+
126
+ if (topCandidate.score < 100) {
127
+ return true
128
+ }
129
+
130
+ return secondCandidate.score >= topCandidate.score - 30
131
+ }
132
+
133
+ function resolveExplicitTableNames(tableNames: string[], explicitTableNames: string[]) {
134
+ if (explicitTableNames.length === 0) {
135
+ return []
136
+ }
137
+
138
+ const normalizedMap = new Map(
139
+ tableNames.map((tableName) => [normalizeText(tableName), tableName])
140
+ )
141
+
142
+ return explicitTableNames
143
+ .map((tableName) => normalizedMap.get(normalizeText(tableName)) ?? tableName)
144
+ .filter((tableName, index, values) => values.indexOf(tableName) === index)
145
+ }
146
+
147
+ export function selectRelevantTables(
148
+ question: string,
149
+ tableNames: string[],
150
+ maxRelevantTables = 12,
151
+ explicitTableNames: string[] = []
152
+ ): TableSelection {
153
+ const terms = extractRelevantTerms(question)
154
+ const resolvedExplicitTables = resolveExplicitTableNames(tableNames, explicitTableNames)
155
+
156
+ const rankedTables: RankedTable[] = tableNames
157
+ .map((name) => ({
158
+ name,
159
+ score: scoreTableName(name, terms) + (resolvedExplicitTables.includes(name) ? 1000 : 0)
160
+ }))
161
+ .sort((left, right) => right.score - left.score || left.name.localeCompare(right.name))
162
+
163
+ const candidateTables = rankedTables
164
+ .filter((table) => table.score > 0)
165
+ .slice(0, maxRelevantTables)
166
+
167
+ const rankedSuggestedTables =
168
+ candidateTables.length > 0
169
+ ? candidateTables.map((table) => table.name)
170
+ : rankedTables.slice(0, maxRelevantTables).map((table) => table.name)
171
+
172
+ const suggestedTables = [...resolvedExplicitTables]
173
+ for (const tableName of rankedSuggestedTables) {
174
+ if (!suggestedTables.includes(tableName)) {
175
+ suggestedTables.push(tableName)
176
+ }
177
+ }
178
+
179
+ return {
180
+ candidateTables,
181
+ suggestedTables: suggestedTables.slice(0, maxRelevantTables),
182
+ requiresConfirmation: resolvedExplicitTables.length === 0 && shouldAskForConfirmation(candidateTables)
183
+ }
184
+ }
@@ -0,0 +1,28 @@
1
+ const READ_ONLY_SQL_PATTERN = /^\s*select\b/i
2
+ const FORBIDDEN_SQL_PATTERN = /\b(insert|update|delete|alter|drop|truncate|create|replace|grant|revoke|execute|call)\b/i
3
+
4
+ export function normalizeReadOnlySql(query: string) {
5
+ return query.trim().replace(/;\s*$/, "")
6
+ }
7
+
8
+ export function validateReadOnlySql(query: string) {
9
+ const normalizedQuery = normalizeReadOnlySql(query)
10
+
11
+ if (!normalizedQuery) {
12
+ throw new Error("Informe uma consulta SQL.")
13
+ }
14
+
15
+ if (!READ_ONLY_SQL_PATTERN.test(normalizedQuery)) {
16
+ throw new Error("Somente consultas SELECT sao permitidas neste modo tecnico.")
17
+ }
18
+
19
+ if (FORBIDDEN_SQL_PATTERN.test(normalizedQuery)) {
20
+ throw new Error("A consulta contem comandos nao permitidos para modo read-only.")
21
+ }
22
+
23
+ if (normalizedQuery.includes(";")) {
24
+ throw new Error("Envie apenas uma consulta por vez.")
25
+ }
26
+
27
+ return normalizedQuery
28
+ }
@@ -0,0 +1,28 @@
1
+ export type SqlQueryPlan = {
2
+ goal: string
3
+ mainTable: string
4
+ relatedTables: string[]
5
+ relationshipFields: string[]
6
+ selectedFields: string[]
7
+ filters: string[]
8
+ orderBy: string[]
9
+ limit: number
10
+ }
11
+
12
+ function formatList(values: string[]) {
13
+ return values.length > 0 ? values.join(", ") : "-"
14
+ }
15
+
16
+ export function formatSqlPlan(plan: SqlQueryPlan) {
17
+ return [
18
+ "Plano de consulta:",
19
+ `- objetivo: ${plan.goal || "-"}`,
20
+ `- tabela principal: ${plan.mainTable || "-"}`,
21
+ `- tabelas relacionadas: ${formatList(plan.relatedTables)}`,
22
+ `- campos de relacionamento: ${formatList(plan.relationshipFields)}`,
23
+ `- colunas de saida: ${formatList(plan.selectedFields)}`,
24
+ `- filtros: ${formatList(plan.filters)}`,
25
+ `- ordenacao: ${formatList(plan.orderBy)}`,
26
+ `- limite: ${plan.limit}`
27
+ ].join("\n")
28
+ }
@@ -0,0 +1,152 @@
1
+ import test from "node:test"
2
+ import assert from "node:assert/strict"
3
+ import { RequestService } from "../services/requestService.js"
4
+ import { resolveAssigneeId } from "../utils/resolveAssigneeId.js"
5
+
6
+ type PostCall = {
7
+ endpoint: string
8
+ body: any
9
+ params?: any
10
+ }
11
+
12
+ type GetCall = {
13
+ endpoint: string
14
+ params?: any
15
+ }
16
+
17
+ class FakeApiClient {
18
+ public getCalls: GetCall[] = []
19
+ public postCalls: PostCall[] = []
20
+ private getResult: any
21
+ private postResult: any
22
+
23
+ constructor(results: { getResult?: any; postResult?: any }) {
24
+ this.getResult = results.getResult
25
+ this.postResult = results.postResult
26
+ }
27
+
28
+ async get(endpoint: string, params?: any) {
29
+ this.getCalls.push({ endpoint, params })
30
+ return this.getResult
31
+ }
32
+
33
+ async post(endpoint: string, body: any = {}, params?: any) {
34
+ this.postCalls.push({ endpoint, body, params })
35
+ return this.postResult
36
+ }
37
+ }
38
+
39
+ test("list usa /api/api/os/listar e aplica filtro de responsavel", async () => {
40
+ const fakeApi = new FakeApiClient({ postResult: [{ id: 1 }] })
41
+ const service = new RequestService(fakeApi as any)
42
+
43
+ await service.list(123)
44
+
45
+ assert.equal(fakeApi.postCalls.length, 1)
46
+ const call = fakeApi.postCalls[0]
47
+
48
+ assert.equal(call.endpoint, "/api/api/os/listar")
49
+ assert.deepEqual(call.body, {})
50
+ assert.equal(call.params?.responsavel, 123)
51
+ assert.match(
52
+ String(call.params?.data_inclusao),
53
+ /^\d{4}-01-01 00:00:00to\d{4}-12-31 23:59:59$/
54
+ )
55
+ })
56
+
57
+ test("list com last_12_months envia data_inclusao em formato valido", async () => {
58
+ const fakeApi = new FakeApiClient({ postResult: [{ id: 1 }] })
59
+ const service = new RequestService(fakeApi as any)
60
+
61
+ await service.list({ assigneeId: 1, period: "last_12_months" })
62
+
63
+ assert.equal(fakeApi.postCalls.length, 1)
64
+ const call = fakeApi.postCalls[0]
65
+ assert.equal(call.endpoint, "/api/api/os/listar")
66
+ assert.match(
67
+ String(call.params?.data_inclusao),
68
+ /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}to\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/
69
+ )
70
+ })
71
+
72
+ test("view usa /api/api/os/listar com codigo", async () => {
73
+ const fakeApi = new FakeApiClient({ postResult: [{ codigo: 999, titulo: "OS 999" }] })
74
+ const service = new RequestService(fakeApi as any)
75
+
76
+ const result = await service.view(999)
77
+
78
+ assert.equal(fakeApi.postCalls.length, 1)
79
+ const call = fakeApi.postCalls[0]
80
+ assert.equal(call.endpoint, "/api/api/os/listar")
81
+ assert.deepEqual(call.body, {})
82
+ assert.deepEqual(call.params, { codigo: 999 })
83
+ assert.deepEqual(result, { codigo: 999, titulo: "OS 999" })
84
+ })
85
+
86
+ test("create usa /api/api/solicitacao_rapida com payload mapeado", async () => {
87
+ const fakeApi = new FakeApiClient({ postResult: { id: 321 } })
88
+ const service = new RequestService(fakeApi as any)
89
+
90
+ const payload = {
91
+ os_tipo: 1,
92
+ os_tipo_servico: 2,
93
+ unidade_gerencial_executora: 3,
94
+ responsavel: 4,
95
+ titulo: "Nova solicitacao",
96
+ cliente: 5,
97
+ data_desejavel: "10/03/2026",
98
+ prioridade: 6,
99
+ observacao: "observacao de teste"
100
+ }
101
+
102
+ const result = await service.create(payload)
103
+
104
+ assert.equal(fakeApi.postCalls.length, 1)
105
+ const call = fakeApi.postCalls[0]
106
+ assert.equal(call.endpoint, "/api/api/solicitacao_rapida")
107
+ assert.deepEqual(call.body, payload)
108
+ assert.deepEqual(result, { id: 321 })
109
+ })
110
+
111
+ test("getTypes usa endpoint real com action/controller", async () => {
112
+ const fakeApi = new FakeApiClient({ getResult: [{ codigo: 1009, descricao: "Abrir Lojas" }] })
113
+ const service = new RequestService(fakeApi as any)
114
+
115
+ const result = await service.getTypes()
116
+
117
+ assert.equal(fakeApi.getCalls.length, 1)
118
+ const call = fakeApi.getCalls[0]
119
+ assert.equal(call.endpoint, "/epa_os/ajax.php")
120
+ assert.deepEqual(call.params, { action: "get", controller: "TipoSolicitacao" })
121
+ assert.deepEqual(result, [{ codigo: 1009, descricao: "Abrir Lojas" }])
122
+ })
123
+
124
+ test("getAssignees monta payload obrigatorio e filtro opcional", async () => {
125
+ const fakeApi = new FakeApiClient({
126
+ getResult: { results: [{ id: 1, text: "suporte.simeon - Suporte Simeon" }] }
127
+ })
128
+ const service = new RequestService(fakeApi as any)
129
+
130
+ const result = await service.getAssignees("suporte", { unitValue: 18 })
131
+
132
+ assert.equal(fakeApi.getCalls.length, 1)
133
+ const call = fakeApi.getCalls[0]
134
+ assert.equal(call.endpoint, "/api/api/usuarios/search")
135
+ assert.deepEqual(call.params, {
136
+ term: "suporte",
137
+ _type: "query",
138
+ q: "suporte",
139
+ "filters[unidade_gerencial][type]": "pertenco",
140
+ "filters[unidade_gerencial][value]": 18
141
+ })
142
+ assert.deepEqual(result, [{ id: 1, text: "suporte.simeon - Suporte Simeon" }])
143
+ })
144
+
145
+ test("resolveAssigneeId resolve login com match exato", () => {
146
+ const assignees = [
147
+ { id: 10, text: "suporte.outro - Outro" },
148
+ { id: 1, text: "suporte.simeon - Suporte Simeon" }
149
+ ]
150
+ const id = resolveAssigneeId(assignees, "suporte.simeon")
151
+ assert.equal(id, 1)
152
+ })
@@ -0,0 +1,25 @@
1
+ import { createTool } from "../../core/createTool.js"
2
+ import { TeamService } from "../../services/teamService.js"
3
+
4
+ const service = new TeamService()
5
+
6
+ export default createTool({
7
+
8
+ name: "analytics.teamReport",
9
+
10
+ description: "Gera relatório de tarefas de um desenvolvedor",
11
+
12
+ schema: {
13
+ name: {
14
+ type: "string",
15
+ description: "Nome do desenvolvedor"
16
+ }
17
+ },
18
+
19
+ handler: async ({ name }) => {
20
+
21
+ return service.teamReport(name)
22
+
23
+ }
24
+
25
+ })
@@ -0,0 +1,59 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath, pathToFileURL } from "url";
4
+
5
+ const currentFilePath = fileURLToPath(import.meta.url);
6
+ const toolsDir = path.dirname(currentFilePath);
7
+
8
+ export async function loadTools() {
9
+
10
+ const tools: any[] = [];
11
+
12
+ async function scan(dir: string) {
13
+
14
+ const files = fs.readdirSync(dir);
15
+
16
+ for (const file of files) {
17
+
18
+ const full = path.join(dir, file);
19
+ const stat = fs.statSync(full);
20
+
21
+ if (stat.isDirectory()) {
22
+ await scan(full);
23
+ continue;
24
+ }
25
+
26
+ if (!file.endsWith(".ts") && !file.endsWith(".js")) {
27
+ continue;
28
+ }
29
+
30
+ if (file.endsWith(".draft.ts") || file.endsWith(".draft.js")) {
31
+ continue;
32
+ }
33
+
34
+ if (file === "loadTools.ts" || file === "loadTools.js") {
35
+ continue;
36
+ }
37
+
38
+ const tool = await import(pathToFileURL(full).href);
39
+
40
+ if (tool.default) {
41
+ tools.push(tool.default);
42
+ }
43
+
44
+ if (tool.tool) {
45
+ tools.push(tool.tool);
46
+ }
47
+
48
+ if (tool.teamReportTool) {
49
+ tools.push(tool.teamReportTool);
50
+ }
51
+
52
+ }
53
+
54
+ }
55
+
56
+ await scan(toolsDir);
57
+
58
+ return tools;
59
+ }
@@ -0,0 +1,59 @@
1
+ import { z } from "zod"
2
+ import { createTool } from "../../core/createTool.js"
3
+ import { RequestService } from "../../services/requestService.js"
4
+
5
+ const service = new RequestService()
6
+
7
+ export default createTool({
8
+
9
+ name: "requests.assignees",
10
+
11
+ description: `
12
+ Busca responsáveis no EPA.
13
+
14
+ Campos obrigatórios:
15
+ - term: texto de pesquisa
16
+ - _type: sempre "query" (preenchido automaticamente)
17
+ - q: texto de pesquisa (usa term por padrão)
18
+
19
+ Filtro opcional preparado:
20
+ - unit_value: ID da unidade gerencial
21
+ - unit_type: padrão "pertenco"
22
+ `,
23
+
24
+ schema: {
25
+ term: {
26
+ type: "string",
27
+ description: "Texto de pesquisa (ex: suporte)"
28
+ },
29
+ q: {
30
+ type: "string",
31
+ description: "Texto de pesquisa alternativo (opcional; padrão: term)"
32
+ },
33
+ unit_value: {
34
+ type: "number",
35
+ description: "ID da unidade gerencial para filtro opcional"
36
+ },
37
+ unit_type: {
38
+ type: "string",
39
+ description: "Tipo do filtro de unidade (opcional; padrao: pertenco)"
40
+ }
41
+ },
42
+ validator: z.object({
43
+ term: z.string().min(1),
44
+ q: z.string().min(1).optional(),
45
+ unit_value: z.number().int().positive().optional(),
46
+ unit_type: z.string().min(1).optional()
47
+ }).strict(),
48
+
49
+ handler: async ({ term, q, unit_value, unit_type }) => {
50
+
51
+ return service.getAssignees(term, {
52
+ q,
53
+ unitValue: unit_value,
54
+ unitType: unit_type
55
+ })
56
+
57
+ }
58
+
59
+ })
@@ -0,0 +1,18 @@
1
+ import { createTool } from "../../core/createTool.js"
2
+ import { RequestService } from "../../services/requestService.js"
3
+
4
+ const service = new RequestService()
5
+
6
+ export default createTool({
7
+
8
+ name: "requests.clients",
9
+
10
+ description: "Lista clientes disponíveis",
11
+
12
+ handler: async () => {
13
+
14
+ return service.getClients()
15
+
16
+ }
17
+
18
+ })
@@ -0,0 +1,59 @@
1
+ import { z } from "zod"
2
+ import { createTool } from "../../core/createTool.js"
3
+ import { RequestService } from "../../services/requestService.js"
4
+
5
+ const service = new RequestService()
6
+
7
+ export default createTool({
8
+ name: "requests.create",
9
+ description: `
10
+ Cria uma nova solicitacao no EPA.
11
+
12
+ Antes de usar esta tool e recomendado consultar:
13
+
14
+ 1. requests.types
15
+ 2. requests.services
16
+ 3. requests.units
17
+ 4. requests.assignees
18
+ 5. requests.clients
19
+ 6. requests.priorities
20
+
21
+ Use os IDs retornados por essas ferramentas.
22
+ `,
23
+ schema: {
24
+ os_tipo: { type: "number", description: "ID do tipo retornado pela tool requests.types" },
25
+ os_tipo_servico: { type: "number", description: "ID de servico retornado pela tool requests.services" },
26
+ unidade_gerencial_executora: { type: "number", description: "ID da unidade retornado pela tool requests.units" },
27
+ responsavel: { type: "number", description: "ID do responsavel retornado pela tool requests.assignees" },
28
+ cliente: { type: "number", description: "ID do cliente retornado pela tool requests.clients" },
29
+ prioridade: { type: "number", description: "ID da prioridade retornado pela tool requests.priorities" },
30
+
31
+ titulo: { type: "string" },
32
+ observacao: { type: "string" },
33
+ data_desejavel: { type: "string", description: "Formato dd/mm/yyyy" }
34
+ },
35
+ validator: z.object({
36
+ os_tipo: z.number().int().positive(),
37
+ os_tipo_servico: z.number().int().positive(),
38
+ unidade_gerencial_executora: z.number().int().positive(),
39
+ responsavel: z.number().int().positive(),
40
+ cliente: z.number().int().positive(),
41
+ prioridade: z.number().int().positive(),
42
+ titulo: z.string().min(3),
43
+ observacao: z.string().optional(),
44
+ data_desejavel: z.string().regex(/^\d{2}\/\d{2}\/\d{4}$/)
45
+ }).strict(),
46
+
47
+ handler: async (args) => {
48
+
49
+ const result = await service.create(args)
50
+
51
+ return {
52
+ message: "Solicitacao criada com sucesso",
53
+ codigo: result.id,
54
+ link: `/exibir_solicitacao.php?codigo=${result.id}`
55
+ }
56
+
57
+ }
58
+
59
+ })
@@ -0,0 +1,57 @@
1
+ import { z } from "zod"
2
+ import { createTool } from "../../core/createTool.js"
3
+ import { RequestService } from "../../services/requestService.js"
4
+ import { resolveAssigneeId } from "../../utils/resolveAssigneeId.js"
5
+
6
+ const service = new RequestService()
7
+
8
+ export default createTool({
9
+ name: "requests.list",
10
+ description: `
11
+ Lista solicitacoes do EPA.
12
+
13
+ Pode filtrar por ID do responsavel ou pelo nome/login do responsavel.
14
+ Tambem permite definir periodo.
15
+
16
+ Para descobrir os responsaveis disponiveis use:
17
+ requests.assignees
18
+ `,
19
+ schema: {
20
+ assignee_id: {
21
+ type: "number",
22
+ description: "ID do responsavel (retornado por requests.assignees)"
23
+ },
24
+ assignee_name: {
25
+ type: "string",
26
+ description: "Nome/login do responsavel (ex: suporte.simeon)"
27
+ },
28
+ period: {
29
+ type: "string",
30
+ description: "Periodo de busca: current_year (padrao) ou last_12_months"
31
+ }
32
+ },
33
+ validator: z.object({
34
+ assignee_id: z.number().int().positive().optional(),
35
+ assignee_name: z.string().min(1).optional(),
36
+ period: z.enum(["current_year", "last_12_months"]).optional()
37
+ }).strict(),
38
+
39
+ handler: async ({ assignee_id, assignee_name, period }) => {
40
+
41
+ if (assignee_id) {
42
+ return service.list({ assigneeId: assignee_id, period })
43
+ }
44
+
45
+ if (assignee_name) {
46
+ const assignees = await service.getAssignees(assignee_name, { q: assignee_name })
47
+ const assigneeId = resolveAssigneeId(assignees, assignee_name)
48
+ if (!assigneeId) {
49
+ return `Responsavel "${assignee_name}" nao encontrado.`
50
+ }
51
+ return service.list({ assigneeId, period })
52
+ }
53
+
54
+ return service.list({ period })
55
+ }
56
+
57
+ })
@@ -0,0 +1,6 @@
1
+ import { createReferenceTool } from "../../core/createReferenceTool.js"
2
+
3
+ export default createReferenceTool(
4
+ "requests.priorities",
5
+ "/api/api/prioridade"
6
+ )