sienge-ecbiesek-mcp 1.1.1__py3-none-any.whl → 1.1.5__py3-none-any.whl
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.
Potentially problematic release.
This version of sienge-ecbiesek-mcp might be problematic. Click here for more details.
- {sienge_ecbiesek_mcp-1.1.1.dist-info → sienge_ecbiesek_mcp-1.1.5.dist-info}/METADATA +30 -8
- sienge_ecbiesek_mcp-1.1.5.dist-info/RECORD +10 -0
- sienge_mcp/metadata.py +43 -0
- sienge_mcp/server.py +938 -326
- sienge_mcp/utils/logger.py +24 -25
- sienge_ecbiesek_mcp-1.1.1.dist-info/RECORD +0 -9
- {sienge_ecbiesek_mcp-1.1.1.dist-info → sienge_ecbiesek_mcp-1.1.5.dist-info}/WHEEL +0 -0
- {sienge_ecbiesek_mcp-1.1.1.dist-info → sienge_ecbiesek_mcp-1.1.5.dist-info}/entry_points.txt +0 -0
- {sienge_ecbiesek_mcp-1.1.1.dist-info → sienge_ecbiesek_mcp-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {sienge_ecbiesek_mcp-1.1.1.dist-info → sienge_ecbiesek_mcp-1.1.5.dist-info}/top_level.txt +0 -0
sienge_mcp/server.py
CHANGED
|
@@ -6,18 +6,15 @@ Suporta Bearer Token e Basic Auth
|
|
|
6
6
|
|
|
7
7
|
from fastmcp import FastMCP
|
|
8
8
|
import httpx
|
|
9
|
-
import asyncio
|
|
10
9
|
from typing import Dict, List, Optional, Any
|
|
11
|
-
import json
|
|
12
10
|
import os
|
|
13
11
|
from dotenv import load_dotenv
|
|
14
12
|
from datetime import datetime
|
|
15
|
-
import base64
|
|
16
13
|
|
|
17
14
|
# Carrega as variáveis de ambiente
|
|
18
15
|
load_dotenv()
|
|
19
16
|
|
|
20
|
-
mcp = FastMCP("Sienge API Integration 🏗️")
|
|
17
|
+
mcp = FastMCP("Sienge API Integration 🏗️ - ChatGPT Compatible")
|
|
21
18
|
|
|
22
19
|
# Configurações da API do Sienge
|
|
23
20
|
SIENGE_BASE_URL = os.getenv("SIENGE_BASE_URL", "https://api.sienge.com.br")
|
|
@@ -27,25 +24,27 @@ SIENGE_PASSWORD = os.getenv("SIENGE_PASSWORD", "")
|
|
|
27
24
|
SIENGE_API_KEY = os.getenv("SIENGE_API_KEY", "")
|
|
28
25
|
REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "30"))
|
|
29
26
|
|
|
27
|
+
|
|
30
28
|
class SiengeAPIError(Exception):
|
|
31
29
|
"""Exceção customizada para erros da API do Sienge"""
|
|
30
|
+
|
|
32
31
|
pass
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
async def make_sienge_request(
|
|
35
|
+
method: str, endpoint: str, params: Optional[Dict] = None, json_data: Optional[Dict] = None
|
|
36
|
+
) -> Dict:
|
|
35
37
|
"""
|
|
36
38
|
Função auxiliar para fazer requisições à API do Sienge (v1)
|
|
37
39
|
Suporta tanto Bearer Token quanto Basic Auth
|
|
38
40
|
"""
|
|
39
41
|
try:
|
|
40
42
|
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as client:
|
|
41
|
-
headers = {
|
|
42
|
-
|
|
43
|
-
"Accept": "application/json"
|
|
44
|
-
}
|
|
45
|
-
|
|
43
|
+
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
|
44
|
+
|
|
46
45
|
# Configurar autenticação e URL
|
|
47
46
|
auth = None
|
|
48
|
-
|
|
47
|
+
|
|
49
48
|
if SIENGE_API_KEY and SIENGE_API_KEY != "sua_api_key_aqui":
|
|
50
49
|
# Bearer Token (Recomendado)
|
|
51
50
|
headers["Authorization"] = f"Bearer {SIENGE_API_KEY}"
|
|
@@ -58,67 +57,44 @@ async def make_sienge_request(method: str, endpoint: str, params: Optional[Dict]
|
|
|
58
57
|
return {
|
|
59
58
|
"success": False,
|
|
60
59
|
"error": "No Authentication",
|
|
61
|
-
"message": "Configure SIENGE_API_KEY ou SIENGE_USERNAME/PASSWORD no .env"
|
|
60
|
+
"message": "Configure SIENGE_API_KEY ou SIENGE_USERNAME/PASSWORD no .env",
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
response = await client.request(
|
|
65
|
-
|
|
66
|
-
url=url,
|
|
67
|
-
headers=headers,
|
|
68
|
-
params=params,
|
|
69
|
-
json=json_data,
|
|
70
|
-
auth=auth
|
|
71
|
-
)
|
|
72
|
-
|
|
62
|
+
|
|
63
|
+
response = await client.request(method=method, url=url, headers=headers, params=params, json=json_data, auth=auth)
|
|
64
|
+
|
|
73
65
|
if response.status_code in [200, 201]:
|
|
74
66
|
try:
|
|
75
|
-
return {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"status_code": response.status_code
|
|
79
|
-
}
|
|
80
|
-
except:
|
|
81
|
-
return {
|
|
82
|
-
"success": True,
|
|
83
|
-
"data": {"message": "Success"},
|
|
84
|
-
"status_code": response.status_code
|
|
85
|
-
}
|
|
67
|
+
return {"success": True, "data": response.json(), "status_code": response.status_code}
|
|
68
|
+
except BaseException:
|
|
69
|
+
return {"success": True, "data": {"message": "Success"}, "status_code": response.status_code}
|
|
86
70
|
else:
|
|
87
71
|
return {
|
|
88
72
|
"success": False,
|
|
89
73
|
"error": f"HTTP {response.status_code}",
|
|
90
74
|
"message": response.text,
|
|
91
|
-
"status_code": response.status_code
|
|
75
|
+
"status_code": response.status_code,
|
|
92
76
|
}
|
|
93
|
-
|
|
77
|
+
|
|
94
78
|
except httpx.TimeoutException:
|
|
95
|
-
return {
|
|
96
|
-
"success": False,
|
|
97
|
-
"error": "Timeout",
|
|
98
|
-
"message": f"A requisição excedeu o tempo limite de {REQUEST_TIMEOUT}s"
|
|
99
|
-
}
|
|
79
|
+
return {"success": False, "error": "Timeout", "message": f"A requisição excedeu o tempo limite de {REQUEST_TIMEOUT}s"}
|
|
100
80
|
except Exception as e:
|
|
101
|
-
return {
|
|
102
|
-
"success": False,
|
|
103
|
-
"error": str(e),
|
|
104
|
-
"message": f"Erro na requisição: {str(e)}"
|
|
105
|
-
}
|
|
81
|
+
return {"success": False, "error": str(e), "message": f"Erro na requisição: {str(e)}"}
|
|
106
82
|
|
|
107
|
-
|
|
83
|
+
|
|
84
|
+
async def make_sienge_bulk_request(
|
|
85
|
+
method: str, endpoint: str, params: Optional[Dict] = None, json_data: Optional[Dict] = None
|
|
86
|
+
) -> Dict:
|
|
108
87
|
"""
|
|
109
88
|
Função auxiliar para fazer requisições à API bulk-data do Sienge
|
|
110
89
|
Suporta tanto Bearer Token quanto Basic Auth
|
|
111
90
|
"""
|
|
112
91
|
try:
|
|
113
92
|
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as client:
|
|
114
|
-
headers = {
|
|
115
|
-
|
|
116
|
-
"Accept": "application/json"
|
|
117
|
-
}
|
|
118
|
-
|
|
93
|
+
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
|
94
|
+
|
|
119
95
|
# Configurar autenticação e URL para bulk-data
|
|
120
96
|
auth = None
|
|
121
|
-
|
|
97
|
+
|
|
122
98
|
if SIENGE_API_KEY and SIENGE_API_KEY != "sua_api_key_aqui":
|
|
123
99
|
# Bearer Token (Recomendado)
|
|
124
100
|
headers["Authorization"] = f"Bearer {SIENGE_API_KEY}"
|
|
@@ -131,61 +107,40 @@ async def make_sienge_bulk_request(method: str, endpoint: str, params: Optional[
|
|
|
131
107
|
return {
|
|
132
108
|
"success": False,
|
|
133
109
|
"error": "No Authentication",
|
|
134
|
-
"message": "Configure SIENGE_API_KEY ou SIENGE_USERNAME/PASSWORD no .env"
|
|
110
|
+
"message": "Configure SIENGE_API_KEY ou SIENGE_USERNAME/PASSWORD no .env",
|
|
135
111
|
}
|
|
136
|
-
|
|
137
|
-
response = await client.request(
|
|
138
|
-
|
|
139
|
-
url=url,
|
|
140
|
-
headers=headers,
|
|
141
|
-
params=params,
|
|
142
|
-
json=json_data,
|
|
143
|
-
auth=auth
|
|
144
|
-
)
|
|
145
|
-
|
|
112
|
+
|
|
113
|
+
response = await client.request(method=method, url=url, headers=headers, params=params, json=json_data, auth=auth)
|
|
114
|
+
|
|
146
115
|
if response.status_code in [200, 201]:
|
|
147
116
|
try:
|
|
148
|
-
return {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"status_code": response.status_code
|
|
152
|
-
}
|
|
153
|
-
except:
|
|
154
|
-
return {
|
|
155
|
-
"success": True,
|
|
156
|
-
"data": {"message": "Success"},
|
|
157
|
-
"status_code": response.status_code
|
|
158
|
-
}
|
|
117
|
+
return {"success": True, "data": response.json(), "status_code": response.status_code}
|
|
118
|
+
except BaseException:
|
|
119
|
+
return {"success": True, "data": {"message": "Success"}, "status_code": response.status_code}
|
|
159
120
|
else:
|
|
160
121
|
return {
|
|
161
122
|
"success": False,
|
|
162
123
|
"error": f"HTTP {response.status_code}",
|
|
163
124
|
"message": response.text,
|
|
164
|
-
"status_code": response.status_code
|
|
125
|
+
"status_code": response.status_code,
|
|
165
126
|
}
|
|
166
|
-
|
|
127
|
+
|
|
167
128
|
except httpx.TimeoutException:
|
|
168
|
-
return {
|
|
169
|
-
"success": False,
|
|
170
|
-
"error": "Timeout",
|
|
171
|
-
"message": f"A requisição excedeu o tempo limite de {REQUEST_TIMEOUT}s"
|
|
172
|
-
}
|
|
129
|
+
return {"success": False, "error": "Timeout", "message": f"A requisição excedeu o tempo limite de {REQUEST_TIMEOUT}s"}
|
|
173
130
|
except Exception as e:
|
|
174
|
-
return {
|
|
175
|
-
|
|
176
|
-
"error": str(e),
|
|
177
|
-
"message": f"Erro na requisição bulk-data: {str(e)}"
|
|
178
|
-
}
|
|
131
|
+
return {"success": False, "error": str(e), "message": f"Erro na requisição bulk-data: {str(e)}"}
|
|
132
|
+
|
|
179
133
|
|
|
180
134
|
# ============ CONEXÃO E TESTE ============
|
|
181
135
|
|
|
136
|
+
|
|
182
137
|
@mcp.tool
|
|
183
138
|
async def test_sienge_connection() -> Dict:
|
|
184
139
|
"""Testa a conexão com a API do Sienge"""
|
|
185
140
|
try:
|
|
186
141
|
# Tentar endpoint mais simples primeiro
|
|
187
142
|
result = await make_sienge_request("GET", "/customer-types")
|
|
188
|
-
|
|
143
|
+
|
|
189
144
|
if result["success"]:
|
|
190
145
|
auth_method = "Bearer Token" if SIENGE_API_KEY else "Basic Auth"
|
|
191
146
|
return {
|
|
@@ -193,7 +148,7 @@ async def test_sienge_connection() -> Dict:
|
|
|
193
148
|
"message": "✅ Conexão com API do Sienge estabelecida com sucesso!",
|
|
194
149
|
"api_status": "Online",
|
|
195
150
|
"auth_method": auth_method,
|
|
196
|
-
"timestamp": datetime.now().isoformat()
|
|
151
|
+
"timestamp": datetime.now().isoformat(),
|
|
197
152
|
}
|
|
198
153
|
else:
|
|
199
154
|
return {
|
|
@@ -201,23 +156,27 @@ async def test_sienge_connection() -> Dict:
|
|
|
201
156
|
"message": "❌ Falha ao conectar com API do Sienge",
|
|
202
157
|
"error": result.get("error"),
|
|
203
158
|
"details": result.get("message"),
|
|
204
|
-
"timestamp": datetime.now().isoformat()
|
|
159
|
+
"timestamp": datetime.now().isoformat(),
|
|
205
160
|
}
|
|
206
161
|
except Exception as e:
|
|
207
162
|
return {
|
|
208
163
|
"success": False,
|
|
209
164
|
"message": "❌ Erro ao testar conexão",
|
|
210
165
|
"error": str(e),
|
|
211
|
-
"timestamp": datetime.now().isoformat()
|
|
166
|
+
"timestamp": datetime.now().isoformat(),
|
|
212
167
|
}
|
|
213
168
|
|
|
169
|
+
|
|
214
170
|
# ============ CLIENTES ============
|
|
215
171
|
|
|
172
|
+
|
|
216
173
|
@mcp.tool
|
|
217
|
-
async def get_sienge_customers(
|
|
174
|
+
async def get_sienge_customers(
|
|
175
|
+
limit: Optional[int] = 50, offset: Optional[int] = 0, search: Optional[str] = None, customer_type_id: Optional[str] = None
|
|
176
|
+
) -> Dict:
|
|
218
177
|
"""
|
|
219
178
|
Busca clientes no Sienge com filtros
|
|
220
|
-
|
|
179
|
+
|
|
221
180
|
Args:
|
|
222
181
|
limit: Máximo de registros (padrão: 50)
|
|
223
182
|
offset: Pular registros (padrão: 0)
|
|
@@ -225,67 +184,70 @@ async def get_sienge_customers(limit: Optional[int] = 50, offset: Optional[int]
|
|
|
225
184
|
customer_type_id: Filtrar por tipo de cliente
|
|
226
185
|
"""
|
|
227
186
|
params = {"limit": min(limit or 50, 200), "offset": offset or 0}
|
|
228
|
-
|
|
187
|
+
|
|
229
188
|
if search:
|
|
230
189
|
params["search"] = search
|
|
231
190
|
if customer_type_id:
|
|
232
191
|
params["customer_type_id"] = customer_type_id
|
|
233
|
-
|
|
192
|
+
|
|
234
193
|
result = await make_sienge_request("GET", "/customers", params=params)
|
|
235
|
-
|
|
194
|
+
|
|
236
195
|
if result["success"]:
|
|
237
196
|
data = result["data"]
|
|
238
197
|
customers = data.get("results", []) if isinstance(data, dict) else data
|
|
239
198
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
240
199
|
total_count = metadata.get("count", len(customers))
|
|
241
|
-
|
|
200
|
+
|
|
242
201
|
return {
|
|
243
202
|
"success": True,
|
|
244
203
|
"message": f"✅ Encontrados {len(customers)} clientes (total: {total_count})",
|
|
245
204
|
"customers": customers,
|
|
246
205
|
"count": len(customers),
|
|
247
|
-
"filters_applied": params
|
|
206
|
+
"filters_applied": params,
|
|
248
207
|
}
|
|
249
|
-
|
|
208
|
+
|
|
250
209
|
return {
|
|
251
210
|
"success": False,
|
|
252
211
|
"message": "❌ Erro ao buscar clientes",
|
|
253
212
|
"error": result.get("error"),
|
|
254
|
-
"details": result.get("message")
|
|
213
|
+
"details": result.get("message"),
|
|
255
214
|
}
|
|
256
215
|
|
|
216
|
+
|
|
257
217
|
@mcp.tool
|
|
258
218
|
async def get_sienge_customer_types() -> Dict:
|
|
259
219
|
"""Lista tipos de clientes disponíveis"""
|
|
260
220
|
result = await make_sienge_request("GET", "/customer-types")
|
|
261
|
-
|
|
221
|
+
|
|
262
222
|
if result["success"]:
|
|
263
223
|
data = result["data"]
|
|
264
224
|
customer_types = data.get("results", []) if isinstance(data, dict) else data
|
|
265
225
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
266
226
|
total_count = metadata.get("count", len(customer_types))
|
|
267
|
-
|
|
227
|
+
|
|
268
228
|
return {
|
|
269
229
|
"success": True,
|
|
270
230
|
"message": f"✅ Encontrados {len(customer_types)} tipos de clientes (total: {total_count})",
|
|
271
231
|
"customer_types": customer_types,
|
|
272
|
-
"count": len(customer_types)
|
|
232
|
+
"count": len(customer_types),
|
|
273
233
|
}
|
|
274
|
-
|
|
234
|
+
|
|
275
235
|
return {
|
|
276
236
|
"success": False,
|
|
277
237
|
"message": "❌ Erro ao buscar tipos de clientes",
|
|
278
238
|
"error": result.get("error"),
|
|
279
|
-
"details": result.get("message")
|
|
239
|
+
"details": result.get("message"),
|
|
280
240
|
}
|
|
281
241
|
|
|
242
|
+
|
|
282
243
|
# ============ CREDORES ============
|
|
283
244
|
|
|
245
|
+
|
|
284
246
|
@mcp.tool
|
|
285
247
|
async def get_sienge_creditors(limit: Optional[int] = 50, offset: Optional[int] = 0, search: Optional[str] = None) -> Dict:
|
|
286
248
|
"""
|
|
287
249
|
Busca credores/fornecedores
|
|
288
|
-
|
|
250
|
+
|
|
289
251
|
Args:
|
|
290
252
|
limit: Máximo de registros (padrão: 50)
|
|
291
253
|
offset: Pular registros (padrão: 0)
|
|
@@ -294,69 +256,80 @@ async def get_sienge_creditors(limit: Optional[int] = 50, offset: Optional[int]
|
|
|
294
256
|
params = {"limit": min(limit or 50, 200), "offset": offset or 0}
|
|
295
257
|
if search:
|
|
296
258
|
params["search"] = search
|
|
297
|
-
|
|
259
|
+
|
|
298
260
|
result = await make_sienge_request("GET", "/creditors", params=params)
|
|
299
|
-
|
|
261
|
+
|
|
300
262
|
if result["success"]:
|
|
301
263
|
data = result["data"]
|
|
302
264
|
creditors = data.get("results", []) if isinstance(data, dict) else data
|
|
303
265
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
304
266
|
total_count = metadata.get("count", len(creditors))
|
|
305
|
-
|
|
267
|
+
|
|
306
268
|
return {
|
|
307
269
|
"success": True,
|
|
308
270
|
"message": f"✅ Encontrados {len(creditors)} credores (total: {total_count})",
|
|
309
271
|
"creditors": creditors,
|
|
310
|
-
"count": len(creditors)
|
|
272
|
+
"count": len(creditors),
|
|
311
273
|
}
|
|
312
|
-
|
|
274
|
+
|
|
313
275
|
return {
|
|
314
276
|
"success": False,
|
|
315
277
|
"message": "❌ Erro ao buscar credores",
|
|
316
278
|
"error": result.get("error"),
|
|
317
|
-
"details": result.get("message")
|
|
279
|
+
"details": result.get("message"),
|
|
318
280
|
}
|
|
319
281
|
|
|
282
|
+
|
|
320
283
|
@mcp.tool
|
|
321
284
|
async def get_sienge_creditor_bank_info(creditor_id: str) -> Dict:
|
|
322
285
|
"""
|
|
323
286
|
Consulta informações bancárias de um credor
|
|
324
|
-
|
|
287
|
+
|
|
325
288
|
Args:
|
|
326
289
|
creditor_id: ID do credor (obrigatório)
|
|
327
290
|
"""
|
|
328
291
|
result = await make_sienge_request("GET", f"/creditors/{creditor_id}/bank-informations")
|
|
329
|
-
|
|
292
|
+
|
|
330
293
|
if result["success"]:
|
|
331
294
|
return {
|
|
332
295
|
"success": True,
|
|
333
296
|
"message": f"✅ Informações bancárias do credor {creditor_id}",
|
|
334
297
|
"creditor_id": creditor_id,
|
|
335
|
-
"bank_info": result["data"]
|
|
298
|
+
"bank_info": result["data"],
|
|
336
299
|
}
|
|
337
|
-
|
|
300
|
+
|
|
338
301
|
return {
|
|
339
302
|
"success": False,
|
|
340
303
|
"message": f"❌ Erro ao buscar info bancária do credor {creditor_id}",
|
|
341
304
|
"error": result.get("error"),
|
|
342
|
-
"details": result.get("message")
|
|
305
|
+
"details": result.get("message"),
|
|
343
306
|
}
|
|
344
307
|
|
|
308
|
+
|
|
345
309
|
# ============ FINANCEIRO ============
|
|
346
310
|
|
|
311
|
+
|
|
347
312
|
@mcp.tool
|
|
348
|
-
async def get_sienge_accounts_receivable(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
313
|
+
async def get_sienge_accounts_receivable(
|
|
314
|
+
start_date: str,
|
|
315
|
+
end_date: str,
|
|
316
|
+
selection_type: str = "D",
|
|
317
|
+
company_id: Optional[int] = None,
|
|
318
|
+
cost_centers_id: Optional[List[int]] = None,
|
|
319
|
+
correction_indexer_id: Optional[int] = None,
|
|
320
|
+
correction_date: Optional[str] = None,
|
|
321
|
+
change_start_date: Optional[str] = None,
|
|
322
|
+
completed_bills: Optional[str] = None,
|
|
323
|
+
origins_ids: Optional[List[str]] = None,
|
|
324
|
+
bearers_id_in: Optional[List[int]] = None,
|
|
325
|
+
bearers_id_not_in: Optional[List[int]] = None,
|
|
326
|
+
) -> Dict:
|
|
354
327
|
"""
|
|
355
328
|
Consulta parcelas do contas a receber via API bulk-data
|
|
356
|
-
|
|
329
|
+
|
|
357
330
|
Args:
|
|
358
331
|
start_date: Data de início do período (YYYY-MM-DD) - OBRIGATÓRIO
|
|
359
|
-
end_date: Data do fim do período (YYYY-MM-DD) - OBRIGATÓRIO
|
|
332
|
+
end_date: Data do fim do período (YYYY-MM-DD) - OBRIGATÓRIO
|
|
360
333
|
selection_type: Seleção da data do período (I=emissão, D=vencimento, P=pagamento, B=competência) - padrão: D
|
|
361
334
|
company_id: Código da empresa
|
|
362
335
|
cost_centers_id: Lista de códigos de centro de custo
|
|
@@ -368,12 +341,8 @@ async def get_sienge_accounts_receivable(start_date: str, end_date: str, selecti
|
|
|
368
341
|
bearers_id_in: Filtrar parcelas com códigos de portador específicos
|
|
369
342
|
bearers_id_not_in: Filtrar parcelas excluindo códigos de portador específicos
|
|
370
343
|
"""
|
|
371
|
-
params = {
|
|
372
|
-
|
|
373
|
-
"endDate": end_date,
|
|
374
|
-
"selectionType": selection_type
|
|
375
|
-
}
|
|
376
|
-
|
|
344
|
+
params = {"startDate": start_date, "endDate": end_date, "selectionType": selection_type}
|
|
345
|
+
|
|
377
346
|
if company_id:
|
|
378
347
|
params["companyId"] = company_id
|
|
379
348
|
if cost_centers_id:
|
|
@@ -392,13 +361,13 @@ async def get_sienge_accounts_receivable(start_date: str, end_date: str, selecti
|
|
|
392
361
|
params["bearersIdIn"] = bearers_id_in
|
|
393
362
|
if bearers_id_not_in:
|
|
394
363
|
params["bearersIdNotIn"] = bearers_id_not_in
|
|
395
|
-
|
|
364
|
+
|
|
396
365
|
result = await make_sienge_bulk_request("GET", "/income", params=params)
|
|
397
|
-
|
|
366
|
+
|
|
398
367
|
if result["success"]:
|
|
399
368
|
data = result["data"]
|
|
400
369
|
income_data = data.get("data", []) if isinstance(data, dict) else data
|
|
401
|
-
|
|
370
|
+
|
|
402
371
|
return {
|
|
403
372
|
"success": True,
|
|
404
373
|
"message": f"✅ Encontradas {len(income_data)} parcelas a receber",
|
|
@@ -406,65 +375,70 @@ async def get_sienge_accounts_receivable(start_date: str, end_date: str, selecti
|
|
|
406
375
|
"count": len(income_data),
|
|
407
376
|
"period": f"{start_date} a {end_date}",
|
|
408
377
|
"selection_type": selection_type,
|
|
409
|
-
"filters": params
|
|
378
|
+
"filters": params,
|
|
410
379
|
}
|
|
411
|
-
|
|
380
|
+
|
|
412
381
|
return {
|
|
413
382
|
"success": False,
|
|
414
383
|
"message": "❌ Erro ao buscar parcelas a receber",
|
|
415
384
|
"error": result.get("error"),
|
|
416
|
-
"details": result.get("message")
|
|
385
|
+
"details": result.get("message"),
|
|
417
386
|
}
|
|
418
387
|
|
|
388
|
+
|
|
419
389
|
@mcp.tool
|
|
420
|
-
async def get_sienge_accounts_receivable_by_bills(
|
|
421
|
-
|
|
390
|
+
async def get_sienge_accounts_receivable_by_bills(
|
|
391
|
+
bills_ids: List[int], correction_indexer_id: Optional[int] = None, correction_date: Optional[str] = None
|
|
392
|
+
) -> Dict:
|
|
422
393
|
"""
|
|
423
394
|
Consulta parcelas dos títulos informados via API bulk-data
|
|
424
|
-
|
|
395
|
+
|
|
425
396
|
Args:
|
|
426
397
|
bills_ids: Lista de códigos dos títulos - OBRIGATÓRIO
|
|
427
398
|
correction_indexer_id: Código do indexador de correção
|
|
428
399
|
correction_date: Data para correção do indexador (YYYY-MM-DD)
|
|
429
400
|
"""
|
|
430
|
-
params = {
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
401
|
+
params = {"billsIds": bills_ids}
|
|
402
|
+
|
|
434
403
|
if correction_indexer_id:
|
|
435
404
|
params["correctionIndexerId"] = correction_indexer_id
|
|
436
405
|
if correction_date:
|
|
437
406
|
params["correctionDate"] = correction_date
|
|
438
|
-
|
|
407
|
+
|
|
439
408
|
result = await make_sienge_bulk_request("GET", "/income/by-bills", params=params)
|
|
440
|
-
|
|
409
|
+
|
|
441
410
|
if result["success"]:
|
|
442
411
|
data = result["data"]
|
|
443
412
|
income_data = data.get("data", []) if isinstance(data, dict) else data
|
|
444
|
-
|
|
413
|
+
|
|
445
414
|
return {
|
|
446
415
|
"success": True,
|
|
447
416
|
"message": f"✅ Encontradas {len(income_data)} parcelas dos títulos informados",
|
|
448
417
|
"income_data": income_data,
|
|
449
418
|
"count": len(income_data),
|
|
450
419
|
"bills_consulted": bills_ids,
|
|
451
|
-
"filters": params
|
|
420
|
+
"filters": params,
|
|
452
421
|
}
|
|
453
|
-
|
|
422
|
+
|
|
454
423
|
return {
|
|
455
424
|
"success": False,
|
|
456
425
|
"message": "❌ Erro ao buscar parcelas dos títulos informados",
|
|
457
426
|
"error": result.get("error"),
|
|
458
|
-
"details": result.get("message")
|
|
427
|
+
"details": result.get("message"),
|
|
459
428
|
}
|
|
460
429
|
|
|
430
|
+
|
|
461
431
|
@mcp.tool
|
|
462
|
-
async def get_sienge_bills(
|
|
463
|
-
|
|
464
|
-
|
|
432
|
+
async def get_sienge_bills(
|
|
433
|
+
start_date: Optional[str] = None,
|
|
434
|
+
end_date: Optional[str] = None,
|
|
435
|
+
creditor_id: Optional[str] = None,
|
|
436
|
+
status: Optional[str] = None,
|
|
437
|
+
limit: Optional[int] = 50,
|
|
438
|
+
) -> Dict:
|
|
465
439
|
"""
|
|
466
440
|
Consulta títulos a pagar (contas a pagar) - REQUER startDate obrigatório
|
|
467
|
-
|
|
441
|
+
|
|
468
442
|
Args:
|
|
469
443
|
start_date: Data inicial obrigatória (YYYY-MM-DD) - padrão últimos 30 dias
|
|
470
444
|
end_date: Data final (YYYY-MM-DD) - padrão hoje
|
|
@@ -473,36 +447,32 @@ async def get_sienge_bills(start_date: Optional[str] = None, end_date: Optional[
|
|
|
473
447
|
limit: Máximo de registros (padrão: 50, máx: 200)
|
|
474
448
|
"""
|
|
475
449
|
from datetime import datetime, timedelta
|
|
476
|
-
|
|
450
|
+
|
|
477
451
|
# Se start_date não fornecido, usar últimos 30 dias
|
|
478
452
|
if not start_date:
|
|
479
|
-
start_date = (datetime.now() - timedelta(days=30)).strftime(
|
|
480
|
-
|
|
453
|
+
start_date = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
|
|
454
|
+
|
|
481
455
|
# Se end_date não fornecido, usar hoje
|
|
482
456
|
if not end_date:
|
|
483
|
-
end_date = datetime.now().strftime(
|
|
484
|
-
|
|
457
|
+
end_date = datetime.now().strftime("%Y-%m-%d")
|
|
458
|
+
|
|
485
459
|
# Parâmetros obrigatórios
|
|
486
|
-
params = {
|
|
487
|
-
|
|
488
|
-
"endDate": end_date,
|
|
489
|
-
"limit": min(limit or 50, 200)
|
|
490
|
-
}
|
|
491
|
-
|
|
460
|
+
params = {"startDate": start_date, "endDate": end_date, "limit": min(limit or 50, 200)} # OBRIGATÓRIO pela API
|
|
461
|
+
|
|
492
462
|
# Parâmetros opcionais
|
|
493
463
|
if creditor_id:
|
|
494
464
|
params["creditor_id"] = creditor_id
|
|
495
465
|
if status:
|
|
496
466
|
params["status"] = status
|
|
497
|
-
|
|
467
|
+
|
|
498
468
|
result = await make_sienge_request("GET", "/bills", params=params)
|
|
499
|
-
|
|
469
|
+
|
|
500
470
|
if result["success"]:
|
|
501
471
|
data = result["data"]
|
|
502
472
|
bills = data.get("results", []) if isinstance(data, dict) else data
|
|
503
473
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
504
474
|
total_count = metadata.get("count", len(bills))
|
|
505
|
-
|
|
475
|
+
|
|
506
476
|
return {
|
|
507
477
|
"success": True,
|
|
508
478
|
"message": f"✅ Encontrados {len(bills)} títulos a pagar (total: {total_count}) - período: {start_date} a {end_date}",
|
|
@@ -510,24 +480,30 @@ async def get_sienge_bills(start_date: Optional[str] = None, end_date: Optional[
|
|
|
510
480
|
"count": len(bills),
|
|
511
481
|
"total_count": total_count,
|
|
512
482
|
"period": {"start_date": start_date, "end_date": end_date},
|
|
513
|
-
"filters": params
|
|
483
|
+
"filters": params,
|
|
514
484
|
}
|
|
515
|
-
|
|
485
|
+
|
|
516
486
|
return {
|
|
517
487
|
"success": False,
|
|
518
488
|
"message": "❌ Erro ao buscar títulos a pagar",
|
|
519
489
|
"error": result.get("error"),
|
|
520
|
-
"details": result.get("message")
|
|
490
|
+
"details": result.get("message"),
|
|
521
491
|
}
|
|
522
492
|
|
|
493
|
+
|
|
523
494
|
# ============ COMPRAS ============
|
|
524
495
|
|
|
496
|
+
|
|
525
497
|
@mcp.tool
|
|
526
|
-
async def get_sienge_purchase_orders(
|
|
527
|
-
|
|
498
|
+
async def get_sienge_purchase_orders(
|
|
499
|
+
purchase_order_id: Optional[str] = None,
|
|
500
|
+
status: Optional[str] = None,
|
|
501
|
+
date_from: Optional[str] = None,
|
|
502
|
+
limit: Optional[int] = 50,
|
|
503
|
+
) -> Dict:
|
|
528
504
|
"""
|
|
529
505
|
Consulta pedidos de compra
|
|
530
|
-
|
|
506
|
+
|
|
531
507
|
Args:
|
|
532
508
|
purchase_order_id: ID específico do pedido
|
|
533
509
|
status: Status do pedido
|
|
@@ -537,73 +513,71 @@ async def get_sienge_purchase_orders(purchase_order_id: Optional[str] = None, st
|
|
|
537
513
|
if purchase_order_id:
|
|
538
514
|
result = await make_sienge_request("GET", f"/purchase-orders/{purchase_order_id}")
|
|
539
515
|
if result["success"]:
|
|
540
|
-
return {
|
|
541
|
-
"success": True,
|
|
542
|
-
"message": f"✅ Pedido {purchase_order_id} encontrado",
|
|
543
|
-
"purchase_order": result["data"]
|
|
544
|
-
}
|
|
516
|
+
return {"success": True, "message": f"✅ Pedido {purchase_order_id} encontrado", "purchase_order": result["data"]}
|
|
545
517
|
return result
|
|
546
|
-
|
|
518
|
+
|
|
547
519
|
params = {"limit": min(limit or 50, 200)}
|
|
548
520
|
if status:
|
|
549
521
|
params["status"] = status
|
|
550
522
|
if date_from:
|
|
551
523
|
params["date_from"] = date_from
|
|
552
|
-
|
|
524
|
+
|
|
553
525
|
result = await make_sienge_request("GET", "/purchase-orders", params=params)
|
|
554
|
-
|
|
526
|
+
|
|
555
527
|
if result["success"]:
|
|
556
528
|
data = result["data"]
|
|
557
529
|
orders = data.get("results", []) if isinstance(data, dict) else data
|
|
558
|
-
|
|
530
|
+
|
|
559
531
|
return {
|
|
560
532
|
"success": True,
|
|
561
533
|
"message": f"✅ Encontrados {len(orders)} pedidos de compra",
|
|
562
534
|
"purchase_orders": orders,
|
|
563
|
-
"count": len(orders)
|
|
535
|
+
"count": len(orders),
|
|
564
536
|
}
|
|
565
|
-
|
|
537
|
+
|
|
566
538
|
return {
|
|
567
539
|
"success": False,
|
|
568
540
|
"message": "❌ Erro ao buscar pedidos de compra",
|
|
569
541
|
"error": result.get("error"),
|
|
570
|
-
"details": result.get("message")
|
|
542
|
+
"details": result.get("message"),
|
|
571
543
|
}
|
|
572
544
|
|
|
545
|
+
|
|
573
546
|
@mcp.tool
|
|
574
547
|
async def get_sienge_purchase_order_items(purchase_order_id: str) -> Dict:
|
|
575
548
|
"""
|
|
576
549
|
Consulta itens de um pedido de compra específico
|
|
577
|
-
|
|
550
|
+
|
|
578
551
|
Args:
|
|
579
552
|
purchase_order_id: ID do pedido (obrigatório)
|
|
580
553
|
"""
|
|
581
554
|
result = await make_sienge_request("GET", f"/purchase-orders/{purchase_order_id}/items")
|
|
582
|
-
|
|
555
|
+
|
|
583
556
|
if result["success"]:
|
|
584
557
|
data = result["data"]
|
|
585
558
|
items = data.get("results", []) if isinstance(data, dict) else data
|
|
586
|
-
|
|
559
|
+
|
|
587
560
|
return {
|
|
588
561
|
"success": True,
|
|
589
562
|
"message": f"✅ Encontrados {len(items)} itens no pedido {purchase_order_id}",
|
|
590
563
|
"purchase_order_id": purchase_order_id,
|
|
591
564
|
"items": items,
|
|
592
|
-
"count": len(items)
|
|
565
|
+
"count": len(items),
|
|
593
566
|
}
|
|
594
|
-
|
|
567
|
+
|
|
595
568
|
return {
|
|
596
569
|
"success": False,
|
|
597
570
|
"message": f"❌ Erro ao buscar itens do pedido {purchase_order_id}",
|
|
598
571
|
"error": result.get("error"),
|
|
599
|
-
"details": result.get("message")
|
|
572
|
+
"details": result.get("message"),
|
|
600
573
|
}
|
|
601
574
|
|
|
575
|
+
|
|
602
576
|
@mcp.tool
|
|
603
577
|
async def get_sienge_purchase_requests(purchase_request_id: Optional[str] = None, limit: Optional[int] = 50) -> Dict:
|
|
604
578
|
"""
|
|
605
579
|
Consulta solicitações de compra
|
|
606
|
-
|
|
580
|
+
|
|
607
581
|
Args:
|
|
608
582
|
purchase_request_id: ID específico da solicitação
|
|
609
583
|
limit: Máximo de registros
|
|
@@ -614,36 +588,37 @@ async def get_sienge_purchase_requests(purchase_request_id: Optional[str] = None
|
|
|
614
588
|
return {
|
|
615
589
|
"success": True,
|
|
616
590
|
"message": f"✅ Solicitação {purchase_request_id} encontrada",
|
|
617
|
-
"purchase_request": result["data"]
|
|
591
|
+
"purchase_request": result["data"],
|
|
618
592
|
}
|
|
619
593
|
return result
|
|
620
|
-
|
|
594
|
+
|
|
621
595
|
params = {"limit": min(limit or 50, 200)}
|
|
622
596
|
result = await make_sienge_request("GET", "/purchase-requests", params=params)
|
|
623
|
-
|
|
597
|
+
|
|
624
598
|
if result["success"]:
|
|
625
599
|
data = result["data"]
|
|
626
600
|
requests = data.get("results", []) if isinstance(data, dict) else data
|
|
627
|
-
|
|
601
|
+
|
|
628
602
|
return {
|
|
629
603
|
"success": True,
|
|
630
604
|
"message": f"✅ Encontradas {len(requests)} solicitações de compra",
|
|
631
605
|
"purchase_requests": requests,
|
|
632
|
-
"count": len(requests)
|
|
606
|
+
"count": len(requests),
|
|
633
607
|
}
|
|
634
|
-
|
|
608
|
+
|
|
635
609
|
return {
|
|
636
610
|
"success": False,
|
|
637
611
|
"message": "❌ Erro ao buscar solicitações de compra",
|
|
638
612
|
"error": result.get("error"),
|
|
639
|
-
"details": result.get("message")
|
|
613
|
+
"details": result.get("message"),
|
|
640
614
|
}
|
|
641
615
|
|
|
616
|
+
|
|
642
617
|
@mcp.tool
|
|
643
618
|
async def create_sienge_purchase_request(description: str, project_id: str, items: List[Dict[str, Any]]) -> Dict:
|
|
644
619
|
"""
|
|
645
620
|
Cria nova solicitação de compra
|
|
646
|
-
|
|
621
|
+
|
|
647
622
|
Args:
|
|
648
623
|
description: Descrição da solicitação
|
|
649
624
|
project_id: ID do projeto/obra
|
|
@@ -653,89 +628,97 @@ async def create_sienge_purchase_request(description: str, project_id: str, item
|
|
|
653
628
|
"description": description,
|
|
654
629
|
"project_id": project_id,
|
|
655
630
|
"items": items,
|
|
656
|
-
"date": datetime.now().strftime("%Y-%m-%d")
|
|
631
|
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
|
657
632
|
}
|
|
658
|
-
|
|
633
|
+
|
|
659
634
|
result = await make_sienge_request("POST", "/purchase-requests", json_data=request_data)
|
|
660
|
-
|
|
635
|
+
|
|
661
636
|
if result["success"]:
|
|
662
637
|
return {
|
|
663
638
|
"success": True,
|
|
664
639
|
"message": "✅ Solicitação de compra criada com sucesso",
|
|
665
640
|
"request_id": result["data"].get("id"),
|
|
666
|
-
"data": result["data"]
|
|
641
|
+
"data": result["data"],
|
|
667
642
|
}
|
|
668
|
-
|
|
643
|
+
|
|
669
644
|
return {
|
|
670
645
|
"success": False,
|
|
671
646
|
"message": "❌ Erro ao criar solicitação de compra",
|
|
672
647
|
"error": result.get("error"),
|
|
673
|
-
"details": result.get("message")
|
|
648
|
+
"details": result.get("message"),
|
|
674
649
|
}
|
|
675
650
|
|
|
651
|
+
|
|
676
652
|
# ============ NOTAS FISCAIS DE COMPRA ============
|
|
677
653
|
|
|
654
|
+
|
|
678
655
|
@mcp.tool
|
|
679
656
|
async def get_sienge_purchase_invoice(sequential_number: int) -> Dict:
|
|
680
657
|
"""
|
|
681
658
|
Consulta nota fiscal de compra por número sequencial
|
|
682
|
-
|
|
659
|
+
|
|
683
660
|
Args:
|
|
684
661
|
sequential_number: Número sequencial da nota fiscal
|
|
685
662
|
"""
|
|
686
663
|
result = await make_sienge_request("GET", f"/purchase-invoices/{sequential_number}")
|
|
687
|
-
|
|
664
|
+
|
|
688
665
|
if result["success"]:
|
|
689
|
-
return {
|
|
690
|
-
|
|
691
|
-
"message": f"✅ Nota fiscal {sequential_number} encontrada",
|
|
692
|
-
"invoice": result["data"]
|
|
693
|
-
}
|
|
694
|
-
|
|
666
|
+
return {"success": True, "message": f"✅ Nota fiscal {sequential_number} encontrada", "invoice": result["data"]}
|
|
667
|
+
|
|
695
668
|
return {
|
|
696
669
|
"success": False,
|
|
697
670
|
"message": f"❌ Erro ao buscar nota fiscal {sequential_number}",
|
|
698
671
|
"error": result.get("error"),
|
|
699
|
-
"details": result.get("message")
|
|
672
|
+
"details": result.get("message"),
|
|
700
673
|
}
|
|
701
674
|
|
|
675
|
+
|
|
702
676
|
@mcp.tool
|
|
703
677
|
async def get_sienge_purchase_invoice_items(sequential_number: int) -> Dict:
|
|
704
678
|
"""
|
|
705
679
|
Consulta itens de uma nota fiscal de compra
|
|
706
|
-
|
|
680
|
+
|
|
707
681
|
Args:
|
|
708
682
|
sequential_number: Número sequencial da nota fiscal
|
|
709
683
|
"""
|
|
710
684
|
result = await make_sienge_request("GET", f"/purchase-invoices/{sequential_number}/items")
|
|
711
|
-
|
|
685
|
+
|
|
712
686
|
if result["success"]:
|
|
713
687
|
data = result["data"]
|
|
714
688
|
items = data.get("results", []) if isinstance(data, dict) else data
|
|
715
689
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
716
|
-
|
|
690
|
+
|
|
717
691
|
return {
|
|
718
692
|
"success": True,
|
|
719
693
|
"message": f"✅ Encontrados {len(items)} itens na nota fiscal {sequential_number}",
|
|
720
694
|
"items": items,
|
|
721
695
|
"count": len(items),
|
|
722
|
-
"metadata": metadata
|
|
696
|
+
"metadata": metadata,
|
|
723
697
|
}
|
|
724
|
-
|
|
698
|
+
|
|
725
699
|
return {
|
|
726
700
|
"success": False,
|
|
727
701
|
"message": f"❌ Erro ao buscar itens da nota fiscal {sequential_number}",
|
|
728
702
|
"error": result.get("error"),
|
|
729
|
-
"details": result.get("message")
|
|
703
|
+
"details": result.get("message"),
|
|
730
704
|
}
|
|
731
705
|
|
|
706
|
+
|
|
732
707
|
@mcp.tool
|
|
733
|
-
async def create_sienge_purchase_invoice(
|
|
734
|
-
|
|
735
|
-
|
|
708
|
+
async def create_sienge_purchase_invoice(
|
|
709
|
+
document_id: str,
|
|
710
|
+
number: str,
|
|
711
|
+
supplier_id: int,
|
|
712
|
+
company_id: int,
|
|
713
|
+
movement_type_id: int,
|
|
714
|
+
movement_date: str,
|
|
715
|
+
issue_date: str,
|
|
716
|
+
series: Optional[str] = None,
|
|
717
|
+
notes: Optional[str] = None,
|
|
718
|
+
) -> Dict:
|
|
736
719
|
"""
|
|
737
720
|
Cadastra uma nova nota fiscal de compra
|
|
738
|
-
|
|
721
|
+
|
|
739
722
|
Args:
|
|
740
723
|
document_id: ID do documento (ex: "NF")
|
|
741
724
|
number: Número da nota fiscal
|
|
@@ -754,37 +737,38 @@ async def create_sienge_purchase_invoice(document_id: str, number: str, supplier
|
|
|
754
737
|
"companyId": company_id,
|
|
755
738
|
"movementTypeId": movement_type_id,
|
|
756
739
|
"movementDate": movement_date,
|
|
757
|
-
"issueDate": issue_date
|
|
740
|
+
"issueDate": issue_date,
|
|
758
741
|
}
|
|
759
|
-
|
|
742
|
+
|
|
760
743
|
if series:
|
|
761
744
|
invoice_data["series"] = series
|
|
762
745
|
if notes:
|
|
763
746
|
invoice_data["notes"] = notes
|
|
764
|
-
|
|
747
|
+
|
|
765
748
|
result = await make_sienge_request("POST", "/purchase-invoices", json_data=invoice_data)
|
|
766
|
-
|
|
749
|
+
|
|
767
750
|
if result["success"]:
|
|
768
|
-
return {
|
|
769
|
-
|
|
770
|
-
"message": f"✅ Nota fiscal {number} criada com sucesso",
|
|
771
|
-
"invoice": result["data"]
|
|
772
|
-
}
|
|
773
|
-
|
|
751
|
+
return {"success": True, "message": f"✅ Nota fiscal {number} criada com sucesso", "invoice": result["data"]}
|
|
752
|
+
|
|
774
753
|
return {
|
|
775
754
|
"success": False,
|
|
776
755
|
"message": f"❌ Erro ao criar nota fiscal {number}",
|
|
777
756
|
"error": result.get("error"),
|
|
778
|
-
"details": result.get("message")
|
|
757
|
+
"details": result.get("message"),
|
|
779
758
|
}
|
|
780
759
|
|
|
760
|
+
|
|
781
761
|
@mcp.tool
|
|
782
|
-
async def add_items_to_purchase_invoice(
|
|
783
|
-
|
|
784
|
-
|
|
762
|
+
async def add_items_to_purchase_invoice(
|
|
763
|
+
sequential_number: int,
|
|
764
|
+
deliveries_order: List[Dict[str, Any]],
|
|
765
|
+
copy_notes_purchase_orders: bool = True,
|
|
766
|
+
copy_notes_resources: bool = False,
|
|
767
|
+
copy_attachments_purchase_orders: bool = True,
|
|
768
|
+
) -> Dict:
|
|
785
769
|
"""
|
|
786
770
|
Insere itens em uma nota fiscal a partir de entregas de pedidos de compra
|
|
787
|
-
|
|
771
|
+
|
|
788
772
|
Args:
|
|
789
773
|
sequential_number: Número sequencial da nota fiscal
|
|
790
774
|
deliveries_order: Lista de entregas com estrutura:
|
|
@@ -801,33 +785,41 @@ async def add_items_to_purchase_invoice(sequential_number: int, deliveries_order
|
|
|
801
785
|
"deliveriesOrder": deliveries_order,
|
|
802
786
|
"copyNotesPurchaseOrders": copy_notes_purchase_orders,
|
|
803
787
|
"copyNotesResources": copy_notes_resources,
|
|
804
|
-
"copyAttachmentsPurchaseOrders": copy_attachments_purchase_orders
|
|
788
|
+
"copyAttachmentsPurchaseOrders": copy_attachments_purchase_orders,
|
|
805
789
|
}
|
|
806
|
-
|
|
807
|
-
result = await make_sienge_request(
|
|
808
|
-
|
|
790
|
+
|
|
791
|
+
result = await make_sienge_request(
|
|
792
|
+
"POST", f"/purchase-invoices/{sequential_number}/items/purchase-orders/delivery-schedules", json_data=item_data
|
|
793
|
+
)
|
|
794
|
+
|
|
809
795
|
if result["success"]:
|
|
810
796
|
return {
|
|
811
797
|
"success": True,
|
|
812
798
|
"message": f"✅ Itens adicionados à nota fiscal {sequential_number} com sucesso",
|
|
813
|
-
"item": result["data"]
|
|
799
|
+
"item": result["data"],
|
|
814
800
|
}
|
|
815
|
-
|
|
801
|
+
|
|
816
802
|
return {
|
|
817
803
|
"success": False,
|
|
818
804
|
"message": f"❌ Erro ao adicionar itens à nota fiscal {sequential_number}",
|
|
819
805
|
"error": result.get("error"),
|
|
820
|
-
"details": result.get("message")
|
|
806
|
+
"details": result.get("message"),
|
|
821
807
|
}
|
|
822
808
|
|
|
809
|
+
|
|
823
810
|
@mcp.tool
|
|
824
|
-
async def get_sienge_purchase_invoices_deliveries_attended(
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
811
|
+
async def get_sienge_purchase_invoices_deliveries_attended(
|
|
812
|
+
bill_id: Optional[int] = None,
|
|
813
|
+
sequential_number: Optional[int] = None,
|
|
814
|
+
purchase_order_id: Optional[int] = None,
|
|
815
|
+
invoice_item_number: Optional[int] = None,
|
|
816
|
+
purchase_order_item_number: Optional[int] = None,
|
|
817
|
+
limit: Optional[int] = 100,
|
|
818
|
+
offset: Optional[int] = 0,
|
|
819
|
+
) -> Dict:
|
|
828
820
|
"""
|
|
829
821
|
Lista entregas atendidas entre pedidos de compra e notas fiscais
|
|
830
|
-
|
|
822
|
+
|
|
831
823
|
Args:
|
|
832
824
|
bill_id: ID do título da nota fiscal
|
|
833
825
|
sequential_number: Número sequencial da nota fiscal
|
|
@@ -838,7 +830,7 @@ async def get_sienge_purchase_invoices_deliveries_attended(bill_id: Optional[int
|
|
|
838
830
|
offset: Deslocamento (padrão: 0)
|
|
839
831
|
"""
|
|
840
832
|
params = {"limit": min(limit or 100, 200), "offset": offset or 0}
|
|
841
|
-
|
|
833
|
+
|
|
842
834
|
if bill_id:
|
|
843
835
|
params["billId"] = bill_id
|
|
844
836
|
if sequential_number:
|
|
@@ -849,37 +841,39 @@ async def get_sienge_purchase_invoices_deliveries_attended(bill_id: Optional[int
|
|
|
849
841
|
params["invoiceItemNumber"] = invoice_item_number
|
|
850
842
|
if purchase_order_item_number:
|
|
851
843
|
params["purchaseOrderItemNumber"] = purchase_order_item_number
|
|
852
|
-
|
|
844
|
+
|
|
853
845
|
result = await make_sienge_request("GET", "/purchase-invoices/deliveries-attended", params=params)
|
|
854
|
-
|
|
846
|
+
|
|
855
847
|
if result["success"]:
|
|
856
848
|
data = result["data"]
|
|
857
849
|
deliveries = data.get("results", []) if isinstance(data, dict) else data
|
|
858
850
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
859
|
-
|
|
851
|
+
|
|
860
852
|
return {
|
|
861
853
|
"success": True,
|
|
862
854
|
"message": f"✅ Encontradas {len(deliveries)} entregas atendidas",
|
|
863
855
|
"deliveries": deliveries,
|
|
864
856
|
"count": len(deliveries),
|
|
865
857
|
"metadata": metadata,
|
|
866
|
-
"filters": params
|
|
858
|
+
"filters": params,
|
|
867
859
|
}
|
|
868
|
-
|
|
860
|
+
|
|
869
861
|
return {
|
|
870
862
|
"success": False,
|
|
871
863
|
"message": "❌ Erro ao buscar entregas atendidas",
|
|
872
864
|
"error": result.get("error"),
|
|
873
|
-
"details": result.get("message")
|
|
865
|
+
"details": result.get("message"),
|
|
874
866
|
}
|
|
875
867
|
|
|
868
|
+
|
|
876
869
|
# ============ ESTOQUE ============
|
|
877
870
|
|
|
871
|
+
|
|
878
872
|
@mcp.tool
|
|
879
873
|
async def get_sienge_stock_inventory(cost_center_id: str, resource_id: Optional[str] = None) -> Dict:
|
|
880
874
|
"""
|
|
881
875
|
Consulta inventário de estoque por centro de custo
|
|
882
|
-
|
|
876
|
+
|
|
883
877
|
Args:
|
|
884
878
|
cost_center_id: ID do centro de custo (obrigatório)
|
|
885
879
|
resource_id: ID do insumo específico (opcional)
|
|
@@ -888,67 +882,75 @@ async def get_sienge_stock_inventory(cost_center_id: str, resource_id: Optional[
|
|
|
888
882
|
endpoint = f"/stock-inventories/{cost_center_id}/items/{resource_id}"
|
|
889
883
|
else:
|
|
890
884
|
endpoint = f"/stock-inventories/{cost_center_id}/items"
|
|
891
|
-
|
|
885
|
+
|
|
892
886
|
result = await make_sienge_request("GET", endpoint)
|
|
893
|
-
|
|
887
|
+
|
|
894
888
|
if result["success"]:
|
|
895
889
|
data = result["data"]
|
|
896
890
|
items = data.get("results", []) if isinstance(data, dict) else data
|
|
897
891
|
count = len(items) if isinstance(items, list) else 1
|
|
898
|
-
|
|
892
|
+
|
|
899
893
|
return {
|
|
900
894
|
"success": True,
|
|
901
895
|
"message": f"✅ Inventário do centro de custo {cost_center_id}",
|
|
902
896
|
"cost_center_id": cost_center_id,
|
|
903
897
|
"inventory": items,
|
|
904
|
-
"count": count
|
|
898
|
+
"count": count,
|
|
905
899
|
}
|
|
906
|
-
|
|
900
|
+
|
|
907
901
|
return {
|
|
908
902
|
"success": False,
|
|
909
903
|
"message": f"❌ Erro ao consultar estoque do centro {cost_center_id}",
|
|
910
904
|
"error": result.get("error"),
|
|
911
|
-
"details": result.get("message")
|
|
905
|
+
"details": result.get("message"),
|
|
912
906
|
}
|
|
913
907
|
|
|
908
|
+
|
|
914
909
|
@mcp.tool
|
|
915
910
|
async def get_sienge_stock_reservations(limit: Optional[int] = 50) -> Dict:
|
|
916
911
|
"""
|
|
917
912
|
Lista reservas de estoque
|
|
918
|
-
|
|
913
|
+
|
|
919
914
|
Args:
|
|
920
915
|
limit: Máximo de registros
|
|
921
916
|
"""
|
|
922
917
|
params = {"limit": min(limit or 50, 200)}
|
|
923
918
|
result = await make_sienge_request("GET", "/stock-reservations", params=params)
|
|
924
|
-
|
|
919
|
+
|
|
925
920
|
if result["success"]:
|
|
926
921
|
data = result["data"]
|
|
927
922
|
reservations = data.get("results", []) if isinstance(data, dict) else data
|
|
928
|
-
|
|
923
|
+
|
|
929
924
|
return {
|
|
930
925
|
"success": True,
|
|
931
926
|
"message": f"✅ Encontradas {len(reservations)} reservas de estoque",
|
|
932
927
|
"reservations": reservations,
|
|
933
|
-
"count": len(reservations)
|
|
928
|
+
"count": len(reservations),
|
|
934
929
|
}
|
|
935
|
-
|
|
930
|
+
|
|
936
931
|
return {
|
|
937
932
|
"success": False,
|
|
938
933
|
"message": "❌ Erro ao buscar reservas de estoque",
|
|
939
934
|
"error": result.get("error"),
|
|
940
|
-
"details": result.get("message")
|
|
935
|
+
"details": result.get("message"),
|
|
941
936
|
}
|
|
942
937
|
|
|
938
|
+
|
|
943
939
|
# ============ PROJETOS/OBRAS ============
|
|
944
940
|
|
|
941
|
+
|
|
945
942
|
@mcp.tool
|
|
946
|
-
async def get_sienge_projects(
|
|
947
|
-
|
|
948
|
-
|
|
943
|
+
async def get_sienge_projects(
|
|
944
|
+
limit: Optional[int] = 100,
|
|
945
|
+
offset: Optional[int] = 0,
|
|
946
|
+
company_id: Optional[int] = None,
|
|
947
|
+
enterprise_type: Optional[int] = None,
|
|
948
|
+
receivable_register: Optional[str] = None,
|
|
949
|
+
only_buildings_enabled: Optional[bool] = False,
|
|
950
|
+
) -> Dict:
|
|
949
951
|
"""
|
|
950
952
|
Busca empreendimentos/obras no Sienge
|
|
951
|
-
|
|
953
|
+
|
|
952
954
|
Args:
|
|
953
955
|
limit: Máximo de registros (padrão: 100, máximo: 200)
|
|
954
956
|
offset: Pular registros (padrão: 0)
|
|
@@ -958,7 +960,7 @@ async def get_sienge_projects(limit: Optional[int] = 100, offset: Optional[int]
|
|
|
958
960
|
only_buildings_enabled: Retornar apenas obras habilitadas para integração orçamentária
|
|
959
961
|
"""
|
|
960
962
|
params = {"limit": min(limit or 100, 200), "offset": offset or 0}
|
|
961
|
-
|
|
963
|
+
|
|
962
964
|
if company_id:
|
|
963
965
|
params["companyId"] = company_id
|
|
964
966
|
if enterprise_type:
|
|
@@ -967,120 +969,125 @@ async def get_sienge_projects(limit: Optional[int] = 100, offset: Optional[int]
|
|
|
967
969
|
params["receivableRegister"] = receivable_register
|
|
968
970
|
if only_buildings_enabled:
|
|
969
971
|
params["onlyBuildingsEnabledForIntegration"] = only_buildings_enabled
|
|
970
|
-
|
|
972
|
+
|
|
971
973
|
result = await make_sienge_request("GET", "/enterprises", params=params)
|
|
972
|
-
|
|
974
|
+
|
|
973
975
|
if result["success"]:
|
|
974
976
|
data = result["data"]
|
|
975
977
|
enterprises = data.get("results", []) if isinstance(data, dict) else data
|
|
976
978
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
977
|
-
|
|
979
|
+
|
|
978
980
|
return {
|
|
979
981
|
"success": True,
|
|
980
982
|
"message": f"✅ Encontrados {len(enterprises)} empreendimentos",
|
|
981
983
|
"enterprises": enterprises,
|
|
982
984
|
"count": len(enterprises),
|
|
983
985
|
"metadata": metadata,
|
|
984
|
-
"filters": params
|
|
986
|
+
"filters": params,
|
|
985
987
|
}
|
|
986
|
-
|
|
988
|
+
|
|
987
989
|
return {
|
|
988
990
|
"success": False,
|
|
989
991
|
"message": "❌ Erro ao buscar empreendimentos",
|
|
990
992
|
"error": result.get("error"),
|
|
991
|
-
"details": result.get("message")
|
|
993
|
+
"details": result.get("message"),
|
|
992
994
|
}
|
|
993
995
|
|
|
996
|
+
|
|
994
997
|
@mcp.tool
|
|
995
998
|
async def get_sienge_enterprise_by_id(enterprise_id: int) -> Dict:
|
|
996
999
|
"""
|
|
997
1000
|
Busca um empreendimento específico por ID no Sienge
|
|
998
|
-
|
|
1001
|
+
|
|
999
1002
|
Args:
|
|
1000
1003
|
enterprise_id: ID do empreendimento
|
|
1001
1004
|
"""
|
|
1002
1005
|
result = await make_sienge_request("GET", f"/enterprises/{enterprise_id}")
|
|
1003
|
-
|
|
1006
|
+
|
|
1004
1007
|
if result["success"]:
|
|
1005
|
-
return {
|
|
1006
|
-
|
|
1007
|
-
"message": f"✅ Empreendimento {enterprise_id} encontrado",
|
|
1008
|
-
"enterprise": result["data"]
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1008
|
+
return {"success": True, "message": f"✅ Empreendimento {enterprise_id} encontrado", "enterprise": result["data"]}
|
|
1009
|
+
|
|
1011
1010
|
return {
|
|
1012
1011
|
"success": False,
|
|
1013
1012
|
"message": f"❌ Erro ao buscar empreendimento {enterprise_id}",
|
|
1014
1013
|
"error": result.get("error"),
|
|
1015
|
-
"details": result.get("message")
|
|
1014
|
+
"details": result.get("message"),
|
|
1016
1015
|
}
|
|
1017
1016
|
|
|
1017
|
+
|
|
1018
1018
|
@mcp.tool
|
|
1019
1019
|
async def get_sienge_enterprise_groupings(enterprise_id: int) -> Dict:
|
|
1020
1020
|
"""
|
|
1021
1021
|
Busca agrupamentos de unidades de um empreendimento específico
|
|
1022
|
-
|
|
1022
|
+
|
|
1023
1023
|
Args:
|
|
1024
1024
|
enterprise_id: ID do empreendimento
|
|
1025
1025
|
"""
|
|
1026
1026
|
result = await make_sienge_request("GET", f"/enterprises/{enterprise_id}/groupings")
|
|
1027
|
-
|
|
1027
|
+
|
|
1028
1028
|
if result["success"]:
|
|
1029
1029
|
groupings = result["data"]
|
|
1030
1030
|
return {
|
|
1031
1031
|
"success": True,
|
|
1032
1032
|
"message": f"✅ Agrupamentos do empreendimento {enterprise_id} encontrados",
|
|
1033
1033
|
"groupings": groupings,
|
|
1034
|
-
"count": len(groupings) if isinstance(groupings, list) else 0
|
|
1034
|
+
"count": len(groupings) if isinstance(groupings, list) else 0,
|
|
1035
1035
|
}
|
|
1036
|
-
|
|
1036
|
+
|
|
1037
1037
|
return {
|
|
1038
1038
|
"success": False,
|
|
1039
1039
|
"message": f"❌ Erro ao buscar agrupamentos do empreendimento {enterprise_id}",
|
|
1040
1040
|
"error": result.get("error"),
|
|
1041
|
-
"details": result.get("message")
|
|
1041
|
+
"details": result.get("message"),
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
1044
|
+
|
|
1044
1045
|
@mcp.tool
|
|
1045
1046
|
async def get_sienge_units(limit: Optional[int] = 50, offset: Optional[int] = 0) -> Dict:
|
|
1046
1047
|
"""
|
|
1047
1048
|
Consulta unidades cadastradas no Sienge
|
|
1048
|
-
|
|
1049
|
+
|
|
1049
1050
|
Args:
|
|
1050
1051
|
limit: Máximo de registros (padrão: 50)
|
|
1051
1052
|
offset: Pular registros (padrão: 0)
|
|
1052
1053
|
"""
|
|
1053
1054
|
params = {"limit": min(limit or 50, 200), "offset": offset or 0}
|
|
1054
1055
|
result = await make_sienge_request("GET", "/units", params=params)
|
|
1055
|
-
|
|
1056
|
+
|
|
1056
1057
|
if result["success"]:
|
|
1057
1058
|
data = result["data"]
|
|
1058
1059
|
units = data.get("results", []) if isinstance(data, dict) else data
|
|
1059
1060
|
metadata = data.get("resultSetMetadata", {}) if isinstance(data, dict) else {}
|
|
1060
1061
|
total_count = metadata.get("count", len(units))
|
|
1061
|
-
|
|
1062
|
+
|
|
1062
1063
|
return {
|
|
1063
1064
|
"success": True,
|
|
1064
1065
|
"message": f"✅ Encontradas {len(units)} unidades (total: {total_count})",
|
|
1065
1066
|
"units": units,
|
|
1066
|
-
"count": len(units)
|
|
1067
|
+
"count": len(units),
|
|
1067
1068
|
}
|
|
1068
|
-
|
|
1069
|
+
|
|
1069
1070
|
return {
|
|
1070
1071
|
"success": False,
|
|
1071
1072
|
"message": "❌ Erro ao buscar unidades",
|
|
1072
1073
|
"error": result.get("error"),
|
|
1073
|
-
"details": result.get("message")
|
|
1074
|
+
"details": result.get("message"),
|
|
1074
1075
|
}
|
|
1075
1076
|
|
|
1077
|
+
|
|
1076
1078
|
# ============ CUSTOS ============
|
|
1077
1079
|
|
|
1080
|
+
|
|
1078
1081
|
@mcp.tool
|
|
1079
|
-
async def get_sienge_unit_cost_tables(
|
|
1080
|
-
|
|
1082
|
+
async def get_sienge_unit_cost_tables(
|
|
1083
|
+
table_code: Optional[str] = None,
|
|
1084
|
+
description: Optional[str] = None,
|
|
1085
|
+
status: Optional[str] = "Active",
|
|
1086
|
+
integration_enabled: Optional[bool] = None,
|
|
1087
|
+
) -> Dict:
|
|
1081
1088
|
"""
|
|
1082
1089
|
Consulta tabelas de custos unitários
|
|
1083
|
-
|
|
1090
|
+
|
|
1084
1091
|
Args:
|
|
1085
1092
|
table_code: Código da tabela (opcional)
|
|
1086
1093
|
description: Descrição da tabela (opcional)
|
|
@@ -1088,80 +1095,684 @@ async def get_sienge_unit_cost_tables(table_code: Optional[str] = None, descript
|
|
|
1088
1095
|
integration_enabled: Se habilitada para integração
|
|
1089
1096
|
"""
|
|
1090
1097
|
params = {"status": status or "Active"}
|
|
1091
|
-
|
|
1098
|
+
|
|
1092
1099
|
if table_code:
|
|
1093
1100
|
params["table_code"] = table_code
|
|
1094
1101
|
if description:
|
|
1095
1102
|
params["description"] = description
|
|
1096
1103
|
if integration_enabled is not None:
|
|
1097
1104
|
params["integration_enabled"] = integration_enabled
|
|
1098
|
-
|
|
1105
|
+
|
|
1099
1106
|
result = await make_sienge_request("GET", "/unit-cost-tables", params=params)
|
|
1100
|
-
|
|
1107
|
+
|
|
1101
1108
|
if result["success"]:
|
|
1102
1109
|
data = result["data"]
|
|
1103
1110
|
tables = data.get("results", []) if isinstance(data, dict) else data
|
|
1104
|
-
|
|
1111
|
+
|
|
1105
1112
|
return {
|
|
1106
1113
|
"success": True,
|
|
1107
1114
|
"message": f"✅ Encontradas {len(tables)} tabelas de custos",
|
|
1108
1115
|
"cost_tables": tables,
|
|
1109
|
-
"count": len(tables)
|
|
1116
|
+
"count": len(tables),
|
|
1110
1117
|
}
|
|
1111
|
-
|
|
1118
|
+
|
|
1112
1119
|
return {
|
|
1113
1120
|
"success": False,
|
|
1114
1121
|
"message": "❌ Erro ao buscar tabelas de custos",
|
|
1115
1122
|
"error": result.get("error"),
|
|
1116
|
-
"details": result.get("message")
|
|
1123
|
+
"details": result.get("message"),
|
|
1117
1124
|
}
|
|
1118
1125
|
|
|
1126
|
+
|
|
1127
|
+
# ============ SEARCH UNIVERSAL (COMPATIBILIDADE CHATGPT) ============
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
@mcp.tool
|
|
1131
|
+
async def search_sienge_data(
|
|
1132
|
+
query: str,
|
|
1133
|
+
entity_type: Optional[str] = None,
|
|
1134
|
+
limit: Optional[int] = 20,
|
|
1135
|
+
filters: Optional[Dict[str, Any]] = None
|
|
1136
|
+
) -> Dict:
|
|
1137
|
+
"""
|
|
1138
|
+
Busca universal no Sienge - compatível com ChatGPT/OpenAI MCP
|
|
1139
|
+
|
|
1140
|
+
Permite buscar em múltiplas entidades do Sienge de forma unificada.
|
|
1141
|
+
|
|
1142
|
+
Args:
|
|
1143
|
+
query: Termo de busca (nome, código, descrição, etc.)
|
|
1144
|
+
entity_type: Tipo de entidade (customers, creditors, projects, bills, purchase_orders, etc.)
|
|
1145
|
+
limit: Máximo de registros (padrão: 20, máximo: 100)
|
|
1146
|
+
filters: Filtros específicos por tipo de entidade
|
|
1147
|
+
"""
|
|
1148
|
+
search_results = []
|
|
1149
|
+
limit = min(limit or 20, 100)
|
|
1150
|
+
|
|
1151
|
+
# Se entity_type específico, buscar apenas nele
|
|
1152
|
+
if entity_type:
|
|
1153
|
+
result = await _search_specific_entity(entity_type, query, limit, filters or {})
|
|
1154
|
+
if result["success"]:
|
|
1155
|
+
return result
|
|
1156
|
+
else:
|
|
1157
|
+
return {
|
|
1158
|
+
"success": False,
|
|
1159
|
+
"message": f"❌ Erro na busca em {entity_type}",
|
|
1160
|
+
"error": result.get("error"),
|
|
1161
|
+
"query": query,
|
|
1162
|
+
"entity_type": entity_type
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
# Busca universal em múltiplas entidades
|
|
1166
|
+
entities_to_search = [
|
|
1167
|
+
("customers", "clientes"),
|
|
1168
|
+
("creditors", "credores/fornecedores"),
|
|
1169
|
+
("projects", "empreendimentos/obras"),
|
|
1170
|
+
("bills", "títulos a pagar"),
|
|
1171
|
+
("purchase_orders", "pedidos de compra")
|
|
1172
|
+
]
|
|
1173
|
+
|
|
1174
|
+
total_found = 0
|
|
1175
|
+
|
|
1176
|
+
for entity_key, entity_name in entities_to_search:
|
|
1177
|
+
try:
|
|
1178
|
+
entity_result = await _search_specific_entity(entity_key, query, min(5, limit), {})
|
|
1179
|
+
if entity_result["success"] and entity_result.get("count", 0) > 0:
|
|
1180
|
+
search_results.append({
|
|
1181
|
+
"entity_type": entity_key,
|
|
1182
|
+
"entity_name": entity_name,
|
|
1183
|
+
"count": entity_result["count"],
|
|
1184
|
+
"results": entity_result["data"][:5], # Limitar a 5 por entidade na busca universal
|
|
1185
|
+
"has_more": entity_result["count"] > 5
|
|
1186
|
+
})
|
|
1187
|
+
total_found += entity_result["count"]
|
|
1188
|
+
except Exception as e:
|
|
1189
|
+
# Continuar com outras entidades se uma falhar
|
|
1190
|
+
continue
|
|
1191
|
+
|
|
1192
|
+
if search_results:
|
|
1193
|
+
return {
|
|
1194
|
+
"success": True,
|
|
1195
|
+
"message": f"✅ Busca '{query}' encontrou resultados em {len(search_results)} entidades (total: {total_found} registros)",
|
|
1196
|
+
"query": query,
|
|
1197
|
+
"total_entities": len(search_results),
|
|
1198
|
+
"total_records": total_found,
|
|
1199
|
+
"results_by_entity": search_results,
|
|
1200
|
+
"suggestion": "Use entity_type para buscar especificamente em uma entidade e obter mais resultados"
|
|
1201
|
+
}
|
|
1202
|
+
else:
|
|
1203
|
+
return {
|
|
1204
|
+
"success": False,
|
|
1205
|
+
"message": f"❌ Nenhum resultado encontrado para '{query}'",
|
|
1206
|
+
"query": query,
|
|
1207
|
+
"searched_entities": [name for _, name in entities_to_search],
|
|
1208
|
+
"suggestion": "Tente termos mais específicos ou use os tools específicos de cada entidade"
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
async def _search_specific_entity(entity_type: str, query: str, limit: int, filters: Dict) -> Dict:
|
|
1213
|
+
"""Função auxiliar para buscar em uma entidade específica"""
|
|
1214
|
+
|
|
1215
|
+
if entity_type == "customers":
|
|
1216
|
+
result = await get_sienge_customers(limit=limit, search=query)
|
|
1217
|
+
if result["success"]:
|
|
1218
|
+
return {
|
|
1219
|
+
"success": True,
|
|
1220
|
+
"data": result["customers"],
|
|
1221
|
+
"count": result["count"],
|
|
1222
|
+
"entity_type": "customers"
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
elif entity_type == "creditors":
|
|
1226
|
+
result = await get_sienge_creditors(limit=limit, search=query)
|
|
1227
|
+
if result["success"]:
|
|
1228
|
+
return {
|
|
1229
|
+
"success": True,
|
|
1230
|
+
"data": result["creditors"],
|
|
1231
|
+
"count": result["count"],
|
|
1232
|
+
"entity_type": "creditors"
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
elif entity_type == "projects" or entity_type == "enterprises":
|
|
1236
|
+
# Para projetos, usar filtros mais específicos se disponível
|
|
1237
|
+
company_id = filters.get("company_id")
|
|
1238
|
+
result = await get_sienge_projects(limit=limit, company_id=company_id)
|
|
1239
|
+
if result["success"]:
|
|
1240
|
+
# Filtrar por query se fornecida
|
|
1241
|
+
projects = result["enterprises"]
|
|
1242
|
+
if query:
|
|
1243
|
+
projects = [
|
|
1244
|
+
p for p in projects
|
|
1245
|
+
if query.lower() in str(p.get("description", "")).lower()
|
|
1246
|
+
or query.lower() in str(p.get("name", "")).lower()
|
|
1247
|
+
or query.lower() in str(p.get("code", "")).lower()
|
|
1248
|
+
]
|
|
1249
|
+
return {
|
|
1250
|
+
"success": True,
|
|
1251
|
+
"data": projects,
|
|
1252
|
+
"count": len(projects),
|
|
1253
|
+
"entity_type": "projects"
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
elif entity_type == "bills":
|
|
1257
|
+
# Para títulos, usar data padrão se não especificada
|
|
1258
|
+
start_date = filters.get("start_date")
|
|
1259
|
+
end_date = filters.get("end_date")
|
|
1260
|
+
result = await get_sienge_bills(
|
|
1261
|
+
start_date=start_date,
|
|
1262
|
+
end_date=end_date,
|
|
1263
|
+
limit=limit
|
|
1264
|
+
)
|
|
1265
|
+
if result["success"]:
|
|
1266
|
+
return {
|
|
1267
|
+
"success": True,
|
|
1268
|
+
"data": result["bills"],
|
|
1269
|
+
"count": result["count"],
|
|
1270
|
+
"entity_type": "bills"
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
elif entity_type == "purchase_orders":
|
|
1274
|
+
result = await get_sienge_purchase_orders(limit=limit)
|
|
1275
|
+
if result["success"]:
|
|
1276
|
+
orders = result["purchase_orders"]
|
|
1277
|
+
# Filtrar por query se fornecida
|
|
1278
|
+
if query:
|
|
1279
|
+
orders = [
|
|
1280
|
+
o for o in orders
|
|
1281
|
+
if query.lower() in str(o.get("description", "")).lower()
|
|
1282
|
+
or query.lower() in str(o.get("id", "")).lower()
|
|
1283
|
+
]
|
|
1284
|
+
return {
|
|
1285
|
+
"success": True,
|
|
1286
|
+
"data": orders,
|
|
1287
|
+
"count": len(orders),
|
|
1288
|
+
"entity_type": "purchase_orders"
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
# Se chegou aqui, entidade não suportada ou erro
|
|
1292
|
+
return {
|
|
1293
|
+
"success": False,
|
|
1294
|
+
"error": f"Entidade '{entity_type}' não suportada ou erro na busca",
|
|
1295
|
+
"supported_entities": ["customers", "creditors", "projects", "bills", "purchase_orders"]
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
@mcp.tool
|
|
1300
|
+
async def list_sienge_entities() -> Dict:
|
|
1301
|
+
"""
|
|
1302
|
+
Lista todas as entidades disponíveis no Sienge MCP para busca
|
|
1303
|
+
|
|
1304
|
+
Retorna informações sobre os tipos de dados que podem ser consultados
|
|
1305
|
+
"""
|
|
1306
|
+
entities = [
|
|
1307
|
+
{
|
|
1308
|
+
"type": "customers",
|
|
1309
|
+
"name": "Clientes",
|
|
1310
|
+
"description": "Clientes cadastrados no sistema",
|
|
1311
|
+
"search_fields": ["nome", "documento", "email"],
|
|
1312
|
+
"tools": ["get_sienge_customers", "search_sienge_data"]
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
"type": "creditors",
|
|
1316
|
+
"name": "Credores/Fornecedores",
|
|
1317
|
+
"description": "Fornecedores e credores cadastrados",
|
|
1318
|
+
"search_fields": ["nome", "documento"],
|
|
1319
|
+
"tools": ["get_sienge_creditors", "get_sienge_creditor_bank_info"]
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
"type": "projects",
|
|
1323
|
+
"name": "Empreendimentos/Obras",
|
|
1324
|
+
"description": "Projetos e obras cadastrados",
|
|
1325
|
+
"search_fields": ["código", "descrição", "nome"],
|
|
1326
|
+
"tools": ["get_sienge_projects", "get_sienge_enterprise_by_id"]
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
"type": "bills",
|
|
1330
|
+
"name": "Títulos a Pagar",
|
|
1331
|
+
"description": "Contas a pagar e títulos financeiros",
|
|
1332
|
+
"search_fields": ["número", "credor", "valor"],
|
|
1333
|
+
"tools": ["get_sienge_bills"]
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
"type": "purchase_orders",
|
|
1337
|
+
"name": "Pedidos de Compra",
|
|
1338
|
+
"description": "Pedidos de compra e solicitações",
|
|
1339
|
+
"search_fields": ["id", "descrição", "status"],
|
|
1340
|
+
"tools": ["get_sienge_purchase_orders", "get_sienge_purchase_requests"]
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
"type": "invoices",
|
|
1344
|
+
"name": "Notas Fiscais",
|
|
1345
|
+
"description": "Notas fiscais de compra",
|
|
1346
|
+
"search_fields": ["número", "série", "fornecedor"],
|
|
1347
|
+
"tools": ["get_sienge_purchase_invoice"]
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
"type": "stock",
|
|
1351
|
+
"name": "Estoque",
|
|
1352
|
+
"description": "Inventário e movimentações de estoque",
|
|
1353
|
+
"search_fields": ["centro_custo", "recurso"],
|
|
1354
|
+
"tools": ["get_sienge_stock_inventory", "get_sienge_stock_reservations"]
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
"type": "financial",
|
|
1358
|
+
"name": "Financeiro",
|
|
1359
|
+
"description": "Contas a receber e movimentações financeiras",
|
|
1360
|
+
"search_fields": ["período", "cliente", "valor"],
|
|
1361
|
+
"tools": ["get_sienge_accounts_receivable"]
|
|
1362
|
+
}
|
|
1363
|
+
]
|
|
1364
|
+
|
|
1365
|
+
return {
|
|
1366
|
+
"success": True,
|
|
1367
|
+
"message": f"✅ {len(entities)} tipos de entidades disponíveis no Sienge",
|
|
1368
|
+
"entities": entities,
|
|
1369
|
+
"total_tools": sum(len(e["tools"]) for e in entities),
|
|
1370
|
+
"usage_example": {
|
|
1371
|
+
"search_all": "search_sienge_data('nome_cliente')",
|
|
1372
|
+
"search_specific": "search_sienge_data('nome_cliente', entity_type='customers')",
|
|
1373
|
+
"direct_access": "get_sienge_customers(search='nome_cliente')"
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
|
|
1378
|
+
# ============ PAGINATION E NAVEGAÇÃO ============
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
@mcp.tool
|
|
1382
|
+
async def get_sienge_data_paginated(
|
|
1383
|
+
entity_type: str,
|
|
1384
|
+
page: int = 1,
|
|
1385
|
+
page_size: int = 20,
|
|
1386
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
1387
|
+
sort_by: Optional[str] = None
|
|
1388
|
+
) -> Dict:
|
|
1389
|
+
"""
|
|
1390
|
+
Busca dados do Sienge com paginação avançada - compatível com ChatGPT
|
|
1391
|
+
|
|
1392
|
+
Args:
|
|
1393
|
+
entity_type: Tipo de entidade (customers, creditors, projects, bills, etc.)
|
|
1394
|
+
page: Número da página (começando em 1)
|
|
1395
|
+
page_size: Registros por página (máximo 50)
|
|
1396
|
+
filters: Filtros específicos da entidade
|
|
1397
|
+
sort_by: Campo para ordenação (se suportado)
|
|
1398
|
+
"""
|
|
1399
|
+
page_size = min(page_size, 50)
|
|
1400
|
+
offset = (page - 1) * page_size
|
|
1401
|
+
|
|
1402
|
+
filters = filters or {}
|
|
1403
|
+
|
|
1404
|
+
# Mapear para os tools existentes com offset
|
|
1405
|
+
if entity_type == "customers":
|
|
1406
|
+
search = filters.get("search")
|
|
1407
|
+
customer_type_id = filters.get("customer_type_id")
|
|
1408
|
+
result = await get_sienge_customers(
|
|
1409
|
+
limit=page_size,
|
|
1410
|
+
offset=offset,
|
|
1411
|
+
search=search,
|
|
1412
|
+
customer_type_id=customer_type_id
|
|
1413
|
+
)
|
|
1414
|
+
|
|
1415
|
+
elif entity_type == "creditors":
|
|
1416
|
+
search = filters.get("search")
|
|
1417
|
+
result = await get_sienge_creditors(
|
|
1418
|
+
limit=page_size,
|
|
1419
|
+
offset=offset,
|
|
1420
|
+
search=search
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
elif entity_type == "projects":
|
|
1424
|
+
result = await get_sienge_projects(
|
|
1425
|
+
limit=page_size,
|
|
1426
|
+
offset=offset,
|
|
1427
|
+
company_id=filters.get("company_id"),
|
|
1428
|
+
enterprise_type=filters.get("enterprise_type")
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1431
|
+
elif entity_type == "bills":
|
|
1432
|
+
result = await get_sienge_bills(
|
|
1433
|
+
start_date=filters.get("start_date"),
|
|
1434
|
+
end_date=filters.get("end_date"),
|
|
1435
|
+
creditor_id=filters.get("creditor_id"),
|
|
1436
|
+
status=filters.get("status"),
|
|
1437
|
+
limit=page_size
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
else:
|
|
1441
|
+
return {
|
|
1442
|
+
"success": False,
|
|
1443
|
+
"message": f"❌ Tipo de entidade '{entity_type}' não suportado para paginação",
|
|
1444
|
+
"supported_types": ["customers", "creditors", "projects", "bills"]
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if result["success"]:
|
|
1448
|
+
# Calcular informações de paginação
|
|
1449
|
+
total_count = result.get("total_count", result.get("count", 0))
|
|
1450
|
+
total_pages = (total_count + page_size - 1) // page_size if total_count > 0 else 1
|
|
1451
|
+
|
|
1452
|
+
return {
|
|
1453
|
+
"success": True,
|
|
1454
|
+
"message": f"✅ Página {page} de {total_pages} - {entity_type}",
|
|
1455
|
+
"data": result.get(entity_type, result.get("data", [])),
|
|
1456
|
+
"pagination": {
|
|
1457
|
+
"current_page": page,
|
|
1458
|
+
"page_size": page_size,
|
|
1459
|
+
"total_pages": total_pages,
|
|
1460
|
+
"total_records": total_count,
|
|
1461
|
+
"has_next": page < total_pages,
|
|
1462
|
+
"has_previous": page > 1,
|
|
1463
|
+
"next_page": page + 1 if page < total_pages else None,
|
|
1464
|
+
"previous_page": page - 1 if page > 1 else None
|
|
1465
|
+
},
|
|
1466
|
+
"entity_type": entity_type,
|
|
1467
|
+
"filters_applied": filters
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
return result
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
@mcp.tool
|
|
1474
|
+
async def search_sienge_financial_data(
|
|
1475
|
+
period_start: str,
|
|
1476
|
+
period_end: str,
|
|
1477
|
+
search_type: str = "both",
|
|
1478
|
+
amount_min: Optional[float] = None,
|
|
1479
|
+
amount_max: Optional[float] = None,
|
|
1480
|
+
customer_creditor_search: Optional[str] = None
|
|
1481
|
+
) -> Dict:
|
|
1482
|
+
"""
|
|
1483
|
+
Busca avançada em dados financeiros do Sienge - Contas a Pagar e Receber
|
|
1484
|
+
|
|
1485
|
+
Args:
|
|
1486
|
+
period_start: Data inicial do período (YYYY-MM-DD)
|
|
1487
|
+
period_end: Data final do período (YYYY-MM-DD)
|
|
1488
|
+
search_type: Tipo de busca ("receivable", "payable", "both")
|
|
1489
|
+
amount_min: Valor mínimo (opcional)
|
|
1490
|
+
amount_max: Valor máximo (opcional)
|
|
1491
|
+
customer_creditor_search: Buscar por nome de cliente/credor (opcional)
|
|
1492
|
+
"""
|
|
1493
|
+
|
|
1494
|
+
financial_results = {
|
|
1495
|
+
"receivable": {"success": False, "data": [], "count": 0, "error": None},
|
|
1496
|
+
"payable": {"success": False, "data": [], "count": 0, "error": None}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
# Buscar contas a receber
|
|
1500
|
+
if search_type in ["receivable", "both"]:
|
|
1501
|
+
try:
|
|
1502
|
+
receivable_result = await get_sienge_accounts_receivable(
|
|
1503
|
+
start_date=period_start,
|
|
1504
|
+
end_date=period_end,
|
|
1505
|
+
selection_type="D" # Por vencimento
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1508
|
+
if receivable_result["success"]:
|
|
1509
|
+
receivable_data = receivable_result["income_data"]
|
|
1510
|
+
|
|
1511
|
+
# Aplicar filtros de valor se especificados
|
|
1512
|
+
if amount_min is not None or amount_max is not None:
|
|
1513
|
+
filtered_data = []
|
|
1514
|
+
for item in receivable_data:
|
|
1515
|
+
amount = float(item.get("amount", 0) or 0)
|
|
1516
|
+
if amount_min is not None and amount < amount_min:
|
|
1517
|
+
continue
|
|
1518
|
+
if amount_max is not None and amount > amount_max:
|
|
1519
|
+
continue
|
|
1520
|
+
filtered_data.append(item)
|
|
1521
|
+
receivable_data = filtered_data
|
|
1522
|
+
|
|
1523
|
+
# Aplicar filtro de cliente se especificado
|
|
1524
|
+
if customer_creditor_search:
|
|
1525
|
+
search_lower = customer_creditor_search.lower()
|
|
1526
|
+
filtered_data = []
|
|
1527
|
+
for item in receivable_data:
|
|
1528
|
+
customer_name = str(item.get("customer_name", "")).lower()
|
|
1529
|
+
if search_lower in customer_name:
|
|
1530
|
+
filtered_data.append(item)
|
|
1531
|
+
receivable_data = filtered_data
|
|
1532
|
+
|
|
1533
|
+
financial_results["receivable"] = {
|
|
1534
|
+
"success": True,
|
|
1535
|
+
"data": receivable_data,
|
|
1536
|
+
"count": len(receivable_data),
|
|
1537
|
+
"error": None
|
|
1538
|
+
}
|
|
1539
|
+
else:
|
|
1540
|
+
financial_results["receivable"]["error"] = receivable_result.get("error")
|
|
1541
|
+
|
|
1542
|
+
except Exception as e:
|
|
1543
|
+
financial_results["receivable"]["error"] = str(e)
|
|
1544
|
+
|
|
1545
|
+
# Buscar contas a pagar
|
|
1546
|
+
if search_type in ["payable", "both"]:
|
|
1547
|
+
try:
|
|
1548
|
+
payable_result = await get_sienge_bills(
|
|
1549
|
+
start_date=period_start,
|
|
1550
|
+
end_date=period_end,
|
|
1551
|
+
limit=100
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1554
|
+
if payable_result["success"]:
|
|
1555
|
+
payable_data = payable_result["bills"]
|
|
1556
|
+
|
|
1557
|
+
# Aplicar filtros de valor se especificados
|
|
1558
|
+
if amount_min is not None or amount_max is not None:
|
|
1559
|
+
filtered_data = []
|
|
1560
|
+
for item in payable_data:
|
|
1561
|
+
amount = float(item.get("amount", 0) or 0)
|
|
1562
|
+
if amount_min is not None and amount < amount_min:
|
|
1563
|
+
continue
|
|
1564
|
+
if amount_max is not None and amount > amount_max:
|
|
1565
|
+
continue
|
|
1566
|
+
filtered_data.append(item)
|
|
1567
|
+
payable_data = filtered_data
|
|
1568
|
+
|
|
1569
|
+
# Aplicar filtro de credor se especificado
|
|
1570
|
+
if customer_creditor_search:
|
|
1571
|
+
search_lower = customer_creditor_search.lower()
|
|
1572
|
+
filtered_data = []
|
|
1573
|
+
for item in payable_data:
|
|
1574
|
+
creditor_name = str(item.get("creditor_name", "")).lower()
|
|
1575
|
+
if search_lower in creditor_name:
|
|
1576
|
+
filtered_data.append(item)
|
|
1577
|
+
payable_data = filtered_data
|
|
1578
|
+
|
|
1579
|
+
financial_results["payable"] = {
|
|
1580
|
+
"success": True,
|
|
1581
|
+
"data": payable_data,
|
|
1582
|
+
"count": len(payable_data),
|
|
1583
|
+
"error": None
|
|
1584
|
+
}
|
|
1585
|
+
else:
|
|
1586
|
+
financial_results["payable"]["error"] = payable_result.get("error")
|
|
1587
|
+
|
|
1588
|
+
except Exception as e:
|
|
1589
|
+
financial_results["payable"]["error"] = str(e)
|
|
1590
|
+
|
|
1591
|
+
# Compilar resultado final
|
|
1592
|
+
total_records = financial_results["receivable"]["count"] + financial_results["payable"]["count"]
|
|
1593
|
+
has_errors = bool(financial_results["receivable"]["error"] or financial_results["payable"]["error"])
|
|
1594
|
+
|
|
1595
|
+
summary = {
|
|
1596
|
+
"period": f"{period_start} a {period_end}",
|
|
1597
|
+
"search_type": search_type,
|
|
1598
|
+
"total_records": total_records,
|
|
1599
|
+
"receivable_count": financial_results["receivable"]["count"],
|
|
1600
|
+
"payable_count": financial_results["payable"]["count"],
|
|
1601
|
+
"filters_applied": {
|
|
1602
|
+
"amount_range": f"{amount_min or 'sem mín'} - {amount_max or 'sem máx'}",
|
|
1603
|
+
"customer_creditor": customer_creditor_search or "todos"
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
if total_records > 0:
|
|
1608
|
+
return {
|
|
1609
|
+
"success": True,
|
|
1610
|
+
"message": f"✅ Busca financeira encontrou {total_records} registros no período",
|
|
1611
|
+
"summary": summary,
|
|
1612
|
+
"receivable": financial_results["receivable"],
|
|
1613
|
+
"payable": financial_results["payable"],
|
|
1614
|
+
"has_errors": has_errors
|
|
1615
|
+
}
|
|
1616
|
+
else:
|
|
1617
|
+
return {
|
|
1618
|
+
"success": False,
|
|
1619
|
+
"message": f"❌ Nenhum registro financeiro encontrado no período {period_start} a {period_end}",
|
|
1620
|
+
"summary": summary,
|
|
1621
|
+
"errors": {
|
|
1622
|
+
"receivable": financial_results["receivable"]["error"],
|
|
1623
|
+
"payable": financial_results["payable"]["error"]
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
|
|
1628
|
+
@mcp.tool
|
|
1629
|
+
async def get_sienge_dashboard_summary() -> Dict:
|
|
1630
|
+
"""
|
|
1631
|
+
Obtém um resumo tipo dashboard com informações gerais do Sienge
|
|
1632
|
+
Útil para visão geral rápida do sistema
|
|
1633
|
+
"""
|
|
1634
|
+
|
|
1635
|
+
# Data atual e períodos
|
|
1636
|
+
today = datetime.now()
|
|
1637
|
+
current_month_start = today.replace(day=1).strftime("%Y-%m-%d")
|
|
1638
|
+
current_month_end = today.strftime("%Y-%m-%d")
|
|
1639
|
+
|
|
1640
|
+
dashboard_data = {}
|
|
1641
|
+
errors = []
|
|
1642
|
+
|
|
1643
|
+
# 1. Testar conexão
|
|
1644
|
+
try:
|
|
1645
|
+
connection_test = await test_sienge_connection()
|
|
1646
|
+
dashboard_data["connection"] = connection_test
|
|
1647
|
+
except Exception as e:
|
|
1648
|
+
errors.append(f"Teste de conexão: {str(e)}")
|
|
1649
|
+
dashboard_data["connection"] = {"success": False, "error": str(e)}
|
|
1650
|
+
|
|
1651
|
+
# 2. Contar clientes (amostra)
|
|
1652
|
+
try:
|
|
1653
|
+
customers_result = await get_sienge_customers(limit=1)
|
|
1654
|
+
if customers_result["success"]:
|
|
1655
|
+
dashboard_data["customers_available"] = True
|
|
1656
|
+
else:
|
|
1657
|
+
dashboard_data["customers_available"] = False
|
|
1658
|
+
except Exception as e:
|
|
1659
|
+
errors.append(f"Clientes: {str(e)}")
|
|
1660
|
+
dashboard_data["customers_available"] = False
|
|
1661
|
+
|
|
1662
|
+
# 3. Contar projetos (amostra)
|
|
1663
|
+
try:
|
|
1664
|
+
projects_result = await get_sienge_projects(limit=5)
|
|
1665
|
+
if projects_result["success"]:
|
|
1666
|
+
dashboard_data["projects"] = {
|
|
1667
|
+
"available": True,
|
|
1668
|
+
"sample_count": len(projects_result["enterprises"]),
|
|
1669
|
+
"total_count": projects_result.get("metadata", {}).get("count", "N/A")
|
|
1670
|
+
}
|
|
1671
|
+
else:
|
|
1672
|
+
dashboard_data["projects"] = {"available": False}
|
|
1673
|
+
except Exception as e:
|
|
1674
|
+
errors.append(f"Projetos: {str(e)}")
|
|
1675
|
+
dashboard_data["projects"] = {"available": False, "error": str(e)}
|
|
1676
|
+
|
|
1677
|
+
# 4. Títulos a pagar do mês atual
|
|
1678
|
+
try:
|
|
1679
|
+
bills_result = await get_sienge_bills(
|
|
1680
|
+
start_date=current_month_start,
|
|
1681
|
+
end_date=current_month_end,
|
|
1682
|
+
limit=10
|
|
1683
|
+
)
|
|
1684
|
+
if bills_result["success"]:
|
|
1685
|
+
dashboard_data["monthly_bills"] = {
|
|
1686
|
+
"available": True,
|
|
1687
|
+
"count": len(bills_result["bills"]),
|
|
1688
|
+
"total_count": bills_result.get("total_count", len(bills_result["bills"]))
|
|
1689
|
+
}
|
|
1690
|
+
else:
|
|
1691
|
+
dashboard_data["monthly_bills"] = {"available": False}
|
|
1692
|
+
except Exception as e:
|
|
1693
|
+
errors.append(f"Títulos mensais: {str(e)}")
|
|
1694
|
+
dashboard_data["monthly_bills"] = {"available": False, "error": str(e)}
|
|
1695
|
+
|
|
1696
|
+
# 5. Tipos de clientes
|
|
1697
|
+
try:
|
|
1698
|
+
customer_types_result = await get_sienge_customer_types()
|
|
1699
|
+
if customer_types_result["success"]:
|
|
1700
|
+
dashboard_data["customer_types"] = {
|
|
1701
|
+
"available": True,
|
|
1702
|
+
"count": len(customer_types_result["customer_types"])
|
|
1703
|
+
}
|
|
1704
|
+
else:
|
|
1705
|
+
dashboard_data["customer_types"] = {"available": False}
|
|
1706
|
+
except Exception as e:
|
|
1707
|
+
dashboard_data["customer_types"] = {"available": False, "error": str(e)}
|
|
1708
|
+
|
|
1709
|
+
# Compilar resultado
|
|
1710
|
+
available_modules = sum(1 for key, value in dashboard_data.items()
|
|
1711
|
+
if key != "connection" and isinstance(value, dict) and value.get("available"))
|
|
1712
|
+
|
|
1713
|
+
return {
|
|
1714
|
+
"success": True,
|
|
1715
|
+
"message": f"✅ Dashboard do Sienge - {available_modules} módulos disponíveis",
|
|
1716
|
+
"timestamp": today.isoformat(),
|
|
1717
|
+
"period_analyzed": f"{current_month_start} a {current_month_end}",
|
|
1718
|
+
"modules_status": dashboard_data,
|
|
1719
|
+
"available_modules": available_modules,
|
|
1720
|
+
"errors": errors if errors else None,
|
|
1721
|
+
"quick_actions": [
|
|
1722
|
+
"search_sienge_data('termo_busca') - Busca universal",
|
|
1723
|
+
"list_sienge_entities() - Listar tipos de dados",
|
|
1724
|
+
"get_sienge_customers(search='nome') - Buscar clientes",
|
|
1725
|
+
"get_sienge_projects() - Listar projetos/obras",
|
|
1726
|
+
"search_sienge_financial_data('2024-01-01', '2024-12-31') - Dados financeiros"
|
|
1727
|
+
]
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
|
|
1119
1731
|
# ============ UTILITÁRIOS ============
|
|
1120
1732
|
|
|
1733
|
+
|
|
1121
1734
|
@mcp.tool
|
|
1122
1735
|
def add(a: int, b: int) -> int:
|
|
1123
1736
|
"""Soma dois números (função de teste)"""
|
|
1124
1737
|
return a + b
|
|
1125
1738
|
|
|
1739
|
+
|
|
1126
1740
|
def _get_auth_info_internal() -> Dict:
|
|
1127
1741
|
"""Função interna para verificar configuração de autenticação"""
|
|
1128
1742
|
if SIENGE_API_KEY and SIENGE_API_KEY != "sua_api_key_aqui":
|
|
1129
|
-
return {
|
|
1130
|
-
"auth_method": "Bearer Token",
|
|
1131
|
-
"configured": True,
|
|
1132
|
-
"base_url": SIENGE_BASE_URL,
|
|
1133
|
-
"api_key_configured": True
|
|
1134
|
-
}
|
|
1743
|
+
return {"auth_method": "Bearer Token", "configured": True, "base_url": SIENGE_BASE_URL, "api_key_configured": True}
|
|
1135
1744
|
elif SIENGE_USERNAME and SIENGE_PASSWORD:
|
|
1136
1745
|
return {
|
|
1137
1746
|
"auth_method": "Basic Auth",
|
|
1138
1747
|
"configured": True,
|
|
1139
1748
|
"base_url": SIENGE_BASE_URL,
|
|
1140
1749
|
"subdomain": SIENGE_SUBDOMAIN,
|
|
1141
|
-
"username": SIENGE_USERNAME
|
|
1750
|
+
"username": SIENGE_USERNAME,
|
|
1142
1751
|
}
|
|
1143
1752
|
else:
|
|
1144
1753
|
return {
|
|
1145
1754
|
"auth_method": "None",
|
|
1146
1755
|
"configured": False,
|
|
1147
|
-
"message": "Configure SIENGE_API_KEY ou SIENGE_USERNAME/PASSWORD no .env"
|
|
1756
|
+
"message": "Configure SIENGE_API_KEY ou SIENGE_USERNAME/PASSWORD no .env",
|
|
1148
1757
|
}
|
|
1149
1758
|
|
|
1759
|
+
|
|
1150
1760
|
@mcp.tool
|
|
1151
1761
|
def get_auth_info() -> Dict:
|
|
1152
1762
|
"""Retorna informações sobre a configuração de autenticação"""
|
|
1153
1763
|
return _get_auth_info_internal()
|
|
1154
1764
|
|
|
1765
|
+
|
|
1155
1766
|
def main():
|
|
1156
1767
|
"""Entry point for the Sienge MCP Server"""
|
|
1157
1768
|
print("* Iniciando Sienge MCP Server (FastMCP)...")
|
|
1158
|
-
|
|
1769
|
+
|
|
1159
1770
|
# Mostrar info de configuração
|
|
1160
1771
|
auth_info = _get_auth_info_internal()
|
|
1161
1772
|
print(f"* Autenticacao: {auth_info['auth_method']}")
|
|
1162
1773
|
print(f"* Configurado: {auth_info['configured']}")
|
|
1163
|
-
|
|
1164
|
-
if not auth_info[
|
|
1774
|
+
|
|
1775
|
+
if not auth_info["configured"]:
|
|
1165
1776
|
print("* ERRO: Autenticacao nao configurada!")
|
|
1166
1777
|
print("Configure as variáveis de ambiente:")
|
|
1167
1778
|
print("- SIENGE_API_KEY (Bearer Token) OU")
|
|
@@ -1179,8 +1790,9 @@ def main():
|
|
|
1179
1790
|
print('export SIENGE_SUBDOMAIN="sua_empresa"')
|
|
1180
1791
|
else:
|
|
1181
1792
|
print("* MCP pronto para uso!")
|
|
1182
|
-
|
|
1793
|
+
|
|
1183
1794
|
mcp.run()
|
|
1184
1795
|
|
|
1796
|
+
|
|
1185
1797
|
if __name__ == "__main__":
|
|
1186
|
-
main()
|
|
1798
|
+
main()
|