next-finance-mcp 0.5.2 → 0.5.4

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.
package/dist/client.d.ts CHANGED
@@ -44,6 +44,36 @@ export declare function listarPlanoDeContas(filtro?: FiltroPlanoDeContas): Promi
44
44
  export declare function listarCentrosDeCusto(busca?: string): Promise<{
45
45
  centros: Record<string, unknown>[];
46
46
  }>;
47
+ export interface FiltroComprasProduto {
48
+ pessoaId?: string;
49
+ dataInicio?: string;
50
+ dataFim?: string;
51
+ busca?: string;
52
+ pagina?: number;
53
+ limite?: number;
54
+ }
55
+ export declare function buscarComprasProduto(opts?: FiltroComprasProduto): Promise<{
56
+ compras: {
57
+ codigo: string;
58
+ produto: string;
59
+ fornecedor: string;
60
+ fornecedor_id: string;
61
+ moeda: string;
62
+ valor: number;
63
+ data: string;
64
+ }[];
65
+ paginacao: {
66
+ pagina: number;
67
+ limite: number;
68
+ total: number;
69
+ paginas: number;
70
+ tem_mais: boolean;
71
+ };
72
+ periodo: {
73
+ inicio: string;
74
+ fim: string;
75
+ };
76
+ }>;
47
77
  export declare function listarTiposConta(): Promise<{
48
78
  tipos: Record<string, unknown>[];
49
79
  }>;
package/dist/client.js CHANGED
@@ -305,7 +305,58 @@ export async function listarCentrosDeCusto(busca) {
305
305
  }
306
306
  return { centros: centros };
307
307
  }
308
- // ── Tipos de Conta ────────────────────────────────────────────────────────────
308
+ export async function buscarComprasProduto(opts = {}) {
309
+ const today = new Date();
310
+ const dataInicio = opts.dataInicio ?? `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-01`;
311
+ const dataFim = opts.dataFim ?? today.toISOString().slice(0, 10);
312
+ const pagina = Math.max(1, opts.pagina ?? 1);
313
+ const limite = Math.min(200, Math.max(1, opts.limite ?? 50));
314
+ const body = {
315
+ DataInicial: dataInicio,
316
+ DataFinal: dataFim,
317
+ };
318
+ if (opts.pessoaId)
319
+ body["PessoaId"] = opts.pessoaId;
320
+ const raw = await apiPost("/api/ProdutoBusiness/FiltrarCompra", body);
321
+ let items = (Array.isArray(raw) ? raw : []);
322
+ // Filtro client-side por nome do produto
323
+ if (opts.busca?.trim()) {
324
+ const t = opts.busca.trim().toLowerCase();
325
+ items = items.filter(item => {
326
+ const prod = item["ProdutoBusiness"];
327
+ const nome = String(prod?.["Nome"] ?? item["Codigo"] ?? "").toLowerCase();
328
+ return nome.includes(t);
329
+ });
330
+ }
331
+ // Paginação client-side
332
+ const total = items.length;
333
+ const offset = (pagina - 1) * limite;
334
+ const paginados = items.slice(offset, offset + limite);
335
+ // Slim: campos essenciais
336
+ const compras = paginados.map(item => {
337
+ const prod = item["ProdutoBusiness"];
338
+ return {
339
+ codigo: String(item["Codigo"] ?? ""),
340
+ produto: String(prod?.["Nome"] ?? ""),
341
+ fornecedor: String(item["PessoaNome"] ?? ""),
342
+ fornecedor_id: String(item["PessoaId"] ?? ""),
343
+ moeda: String(item["MoedaNome"] ?? "BRL"),
344
+ valor: Number(item["Valor"] ?? 0),
345
+ data: String(item["DataAtualizacao"] ?? "").slice(0, 10),
346
+ };
347
+ });
348
+ return {
349
+ compras,
350
+ paginacao: {
351
+ pagina,
352
+ limite,
353
+ total,
354
+ paginas: Math.ceil(total / limite),
355
+ tem_mais: offset + limite < total,
356
+ },
357
+ periodo: { inicio: dataInicio, fim: dataFim },
358
+ };
359
+ }
309
360
  export async function listarTiposConta() {
310
361
  const raw = await apiGet("/api/TipoConta");
311
362
  const tipos = Array.isArray(raw) ? raw : Object.values(raw).find(v => Array.isArray(v)) ?? [];
@@ -364,6 +415,18 @@ export async function buscarLancamentos(opts) {
364
415
  lancamentos = Array.isArray(raw) ? raw
365
416
  : (Object.values(raw).find(v => Array.isArray(v)) ?? []);
366
417
  }
418
+ // Helpers para verificar plano/centro tanto no lançamento raiz quanto nas divisões
419
+ // (lançamentos rateados têm NomePlanoDeContas vazio no raiz e preenchido em ListaLancamentoDivisao)
420
+ function nomesPlano(l) {
421
+ const raiz = String(l["NomePlanoDeContas"] ?? "");
422
+ const divs = l["ListaLancamentoDivisao"] ?? [];
423
+ return [raiz, ...divs.map(d => String(d["NomePlanoDeContas"] ?? ""))].filter(Boolean);
424
+ }
425
+ function nomesCentro(l) {
426
+ const raiz = String(l["NomeCentroDeCustos"] ?? "");
427
+ const divs = l["ListaLancamentoDivisao"] ?? [];
428
+ return [raiz, ...divs.map(d => String(d["NomeCentroDeCustos"] ?? ""))].filter(Boolean);
429
+ }
367
430
  // Filtros client-side (a API não filtra por plano/centro/tipo)
368
431
  const excluirTransf = opts.excluirTransferencias !== false; // default: true
369
432
  if (excluirTransf)
@@ -376,11 +439,12 @@ export async function buscarLancamentos(opts) {
376
439
  lancamentos = lancamentos.filter(l => l["Receita"]);
377
440
  if (opts.planoDeContas?.trim()) {
378
441
  const t = opts.planoDeContas.trim().toLowerCase();
379
- lancamentos = lancamentos.filter(l => String(l["NomePlanoDeContas"] ?? "").toLowerCase().includes(t));
442
+ // inclui lançamento se plano bate no raiz OU em qualquer divisão
443
+ lancamentos = lancamentos.filter(l => nomesPlano(l).some(n => n.toLowerCase().includes(t)));
380
444
  }
381
445
  if (opts.centroDeCusto?.trim()) {
382
446
  const t = opts.centroDeCusto.trim().toLowerCase();
383
- lancamentos = lancamentos.filter(l => String(l["NomeCentroDeCustos"] ?? "").toLowerCase().includes(t));
447
+ lancamentos = lancamentos.filter(l => nomesCentro(l).some(n => n.toLowerCase().includes(t)));
384
448
  }
385
449
  if (opts.busca?.trim()) {
386
450
  const t = opts.busca.trim().toLowerCase();
package/dist/index.js CHANGED
@@ -109,6 +109,24 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
109
109
  },
110
110
  },
111
111
  },
112
+ {
113
+ name: "buscar_compras_produto",
114
+ description: "Busca compras de produtos agrupadas por produto e fornecedor. " +
115
+ "Use 'busca' para filtrar pelo nome do produto (ex: 'bombom garoto', 'arroz'). " +
116
+ "Use 'pessoa_id' para filtrar por fornecedor específico. " +
117
+ "Ideal para ver histórico de compras, comparar preços e rastrear aquisições.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ busca: { type: "string", description: "Filtra pelo nome do produto (ex: 'bombom garoto', 'leite')" },
122
+ pessoa_id: { type: "string", description: "ID do fornecedor (opcional)" },
123
+ data_inicio: { type: "string", description: "Data início YYYY-MM-DD (padrão: 1º do mês atual)" },
124
+ data_fim: { type: "string", description: "Data fim YYYY-MM-DD (padrão: hoje)" },
125
+ pagina: { type: "number", description: "Página (começa em 1, padrão: 1)" },
126
+ limite: { type: "number", description: "Itens por página (padrão: 50, máximo: 200)" },
127
+ },
128
+ },
129
+ },
112
130
  ],
113
131
  }));
114
132
  // ── Helpers ──────────────────────────────────────────────────────────────────
@@ -222,6 +240,22 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
222
240
  limite: args["limite"],
223
241
  }));
224
242
  }
243
+ case "buscar_compras_produto": {
244
+ const authErr = requireAuth();
245
+ if (authErr)
246
+ return authErr;
247
+ const walErr = requireWallet();
248
+ if (walErr)
249
+ return walErr;
250
+ return ok(await client.buscarComprasProduto({
251
+ pessoaId: args["pessoa_id"],
252
+ dataInicio: args["data_inicio"],
253
+ dataFim: args["data_fim"],
254
+ busca: args["busca"],
255
+ pagina: args["pagina"],
256
+ limite: args["limite"],
257
+ }));
258
+ }
225
259
  default:
226
260
  return ok({ erro: `Ferramenta desconhecida: ${name}` });
227
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-finance-mcp",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "mcpName": "io.github.paivapiovesan/next-finance",
5
5
  "description": "MCP Server para o NEXT Finance (finance.net.br) — login via browser, listagem de carteiras, contas e lançamentos",
6
6
  "type": "module",