next-finance-mcp 0.9.39 → 0.9.41
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 +45 -8
- package/dist/client.js +155 -52
- package/dist/index.js +75 -10
- package/package.json +2 -1
package/dist/client.d.ts
CHANGED
|
@@ -123,6 +123,7 @@ export interface CriteriosLancamento {
|
|
|
123
123
|
data: string;
|
|
124
124
|
valor?: number;
|
|
125
125
|
descricao?: string;
|
|
126
|
+
moeda?: string;
|
|
126
127
|
}
|
|
127
128
|
/** Edita um lançamento existente. Localiza por critérios e aplica as alterações. */
|
|
128
129
|
export interface EditarLancamentoOpts extends CriteriosLancamento {
|
|
@@ -364,6 +365,7 @@ export declare function buscarLancamentos(opts: FiltroLancamentos): Promise<Resu
|
|
|
364
365
|
export interface TituloSlim {
|
|
365
366
|
vencimento: string;
|
|
366
367
|
valor: number;
|
|
368
|
+
moeda: string;
|
|
367
369
|
contraparte: string;
|
|
368
370
|
conta: string;
|
|
369
371
|
em_cobranca: boolean;
|
|
@@ -378,12 +380,21 @@ export interface FiltroTitulos {
|
|
|
378
380
|
dataInicio?: string;
|
|
379
381
|
dataFim?: string;
|
|
380
382
|
busca?: string;
|
|
383
|
+
moeda?: string;
|
|
381
384
|
apenasVencidos?: boolean;
|
|
382
385
|
incluirCobranca?: boolean;
|
|
383
386
|
apenasCobranca?: boolean;
|
|
384
387
|
pagina?: number;
|
|
385
388
|
limite?: number;
|
|
386
389
|
}
|
|
390
|
+
export interface TotaisTitulosMoeda {
|
|
391
|
+
moeda: string;
|
|
392
|
+
quantidade: number;
|
|
393
|
+
valor_total: number;
|
|
394
|
+
valor_vencido: number;
|
|
395
|
+
valor_a_vencer: number;
|
|
396
|
+
em_cobranca: number;
|
|
397
|
+
}
|
|
387
398
|
export interface ResultadoTitulos {
|
|
388
399
|
titulos: TituloSlim[];
|
|
389
400
|
paginacao: PaginaMeta;
|
|
@@ -391,15 +402,10 @@ export interface ResultadoTitulos {
|
|
|
391
402
|
inicio: string;
|
|
392
403
|
fim: string;
|
|
393
404
|
};
|
|
394
|
-
|
|
395
|
-
quantidade: number;
|
|
396
|
-
valor_total: number;
|
|
397
|
-
valor_vencido: number;
|
|
398
|
-
valor_a_vencer: number;
|
|
399
|
-
em_cobranca: number;
|
|
400
|
-
};
|
|
405
|
+
totais_por_moeda: TotaisTitulosMoeda[];
|
|
401
406
|
por_contraparte?: {
|
|
402
407
|
contraparte: string;
|
|
408
|
+
moeda: string;
|
|
403
409
|
valor: number;
|
|
404
410
|
quantidade: number;
|
|
405
411
|
vencido: number;
|
|
@@ -407,15 +413,43 @@ export interface ResultadoTitulos {
|
|
|
407
413
|
}
|
|
408
414
|
export declare const buscarContasAPagar: (o?: FiltroTitulos) => Promise<ResultadoTitulos>;
|
|
409
415
|
export declare const buscarContasAReceber: (o?: FiltroTitulos) => Promise<ResultadoTitulos>;
|
|
416
|
+
export interface PeriodicoSlim {
|
|
417
|
+
descricao: string;
|
|
418
|
+
valor_estimado: number;
|
|
419
|
+
moeda: string;
|
|
420
|
+
dia: number;
|
|
421
|
+
periodicidade: string;
|
|
422
|
+
plano_de_contas?: string;
|
|
423
|
+
centro_de_custo?: string;
|
|
424
|
+
variacao_tolerada_pct?: number;
|
|
425
|
+
previsoes_associadas: number;
|
|
426
|
+
observacoes?: string;
|
|
427
|
+
}
|
|
428
|
+
export interface FiltroPeriodicos {
|
|
429
|
+
busca?: string;
|
|
430
|
+
moeda?: string;
|
|
431
|
+
}
|
|
432
|
+
export interface ResultadoPeriodicos {
|
|
433
|
+
periodicos: PeriodicoSlim[];
|
|
434
|
+
estimativa_mensal_por_moeda: {
|
|
435
|
+
moeda: string;
|
|
436
|
+
quantidade: number;
|
|
437
|
+
valor: number;
|
|
438
|
+
}[];
|
|
439
|
+
}
|
|
440
|
+
export declare const buscarDespesasPeriodicas: (o?: FiltroPeriodicos) => Promise<ResultadoPeriodicos>;
|
|
441
|
+
export declare const buscarReceitasPeriodicas: (o?: FiltroPeriodicos) => Promise<ResultadoPeriodicos>;
|
|
410
442
|
export interface ClienteSlim {
|
|
411
443
|
nome: string;
|
|
412
444
|
tipo: "PF" | "PJ";
|
|
445
|
+
moeda: string;
|
|
413
446
|
titulos_a_receber: number;
|
|
414
447
|
valor_a_receber: number;
|
|
415
448
|
valor_vencido: number;
|
|
416
449
|
}
|
|
417
450
|
export interface FiltroClientes {
|
|
418
451
|
busca?: string;
|
|
452
|
+
moeda?: string;
|
|
419
453
|
pagina?: number;
|
|
420
454
|
limite?: number;
|
|
421
455
|
}
|
|
@@ -423,7 +457,10 @@ export interface FiltroClientes {
|
|
|
423
457
|
export declare function listarClientes(opts?: FiltroClientes): Promise<{
|
|
424
458
|
clientes: ClienteSlim[];
|
|
425
459
|
paginacao: PaginaMeta;
|
|
426
|
-
|
|
460
|
+
total_a_receber_por_moeda: {
|
|
461
|
+
moeda: string;
|
|
462
|
+
valor: number;
|
|
463
|
+
}[];
|
|
427
464
|
}>;
|
|
428
465
|
export interface UltimoPrecoProdutoOpts {
|
|
429
466
|
busca: string;
|
package/dist/client.js
CHANGED
|
@@ -1004,38 +1004,58 @@ export async function criarTransferencia(o) {
|
|
|
1004
1004
|
};
|
|
1005
1005
|
}
|
|
1006
1006
|
/** Localiza UM lançamento por critérios. Retorna ID interno (não exposto).
|
|
1007
|
-
* Usado por editar/excluir → exige permissão Editar na conta.
|
|
1007
|
+
* Usado por editar/excluir → exige permissão Editar na conta.
|
|
1008
|
+
* IMPORTANTE: há contas HOMÔNIMAS (ex.: 5 contas "Conta a Pagar", uma por moeda).
|
|
1009
|
+
* Por isso varremos TODAS as contas cujo nome casa e desambiguamos por
|
|
1010
|
+
* valor/descrição/moeda — assim a edição funciona mesmo com nomes repetidos. */
|
|
1008
1011
|
async function localizarLancamento(crit) {
|
|
1009
|
-
const {
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1012
|
+
const { contas } = await fetchContas();
|
|
1013
|
+
const me = await obterUsuarioLogado();
|
|
1014
|
+
const termo = crit.nomeConta.trim().toLowerCase();
|
|
1015
|
+
const candContas = contas
|
|
1016
|
+
.filter(isContaAtiva)
|
|
1017
|
+
.filter(c => permissaoDaConta(c, me) === "Editar")
|
|
1018
|
+
.filter(c => String(c["Nome"] ?? c["NomeConta"] ?? "").toLowerCase().includes(termo));
|
|
1019
|
+
if (candContas.length === 0) {
|
|
1020
|
+
throw new Error(`Conta "${crit.nomeConta}" não encontrada (ou sem permissão de edição).`);
|
|
1021
|
+
}
|
|
1022
|
+
// Resolve moeda de cada conta candidata (para desambiguar e filtrar)
|
|
1023
|
+
const mapaMoedas = await obterMapaMoedas();
|
|
1024
|
+
const moedaDaConta = (c) => {
|
|
1025
|
+
const m = mapaMoedas.get(String(c["IdMoeda"] ?? ""));
|
|
1026
|
+
return (m?.sigla || m?.nome || String(c["NomeMoeda"] ?? "")).trim();
|
|
1015
1027
|
};
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1028
|
+
// Filtra por moeda, se informada (ex.: "BRL", "Real")
|
|
1029
|
+
let contasBusca = candContas;
|
|
1030
|
+
if (crit.moeda?.trim()) {
|
|
1031
|
+
const tm = normalizar(crit.moeda);
|
|
1032
|
+
contasBusca = candContas.filter(c => normalizar(moedaDaConta(c)).includes(tm));
|
|
1033
|
+
if (contasBusca.length === 0) {
|
|
1034
|
+
const disp = [...new Set(candContas.map(moedaDaConta))].join(", ");
|
|
1035
|
+
throw new Error(`Nenhuma conta "${crit.nomeConta}" na moeda "${crit.moeda}". Moedas disponíveis: ${disp}`);
|
|
1036
|
+
}
|
|
1020
1037
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1038
|
+
// Busca lançamentos na data em cada conta candidata
|
|
1039
|
+
const all = [];
|
|
1040
|
+
for (const c of contasBusca) {
|
|
1041
|
+
const idConta = String(c["Id"] ?? "");
|
|
1042
|
+
if (!idConta)
|
|
1043
|
+
continue;
|
|
1044
|
+
try {
|
|
1045
|
+
const lancs = await fetchLancamentosWebDeUmaConta(idConta, crit.data, crit.data);
|
|
1046
|
+
for (const l of lancs) {
|
|
1047
|
+
if (!l["NomeConta"])
|
|
1048
|
+
l["NomeConta"] = c["Nome"];
|
|
1049
|
+
l["__moeda"] = moedaDaConta(c);
|
|
1050
|
+
}
|
|
1051
|
+
all.push(...lancs);
|
|
1052
|
+
}
|
|
1053
|
+
catch { /* ignora conta que falhou; segue nas demais */ }
|
|
1033
1054
|
}
|
|
1034
1055
|
// Filtra por valor e descrição
|
|
1035
|
-
let cand =
|
|
1036
|
-
if (crit.valor !== undefined)
|
|
1056
|
+
let cand = all;
|
|
1057
|
+
if (crit.valor !== undefined)
|
|
1037
1058
|
cand = cand.filter(l => Math.abs(Number(l["Valor"]) - crit.valor) < 0.001);
|
|
1038
|
-
}
|
|
1039
1059
|
if (crit.descricao?.trim()) {
|
|
1040
1060
|
const t = crit.descricao.trim().toLowerCase();
|
|
1041
1061
|
cand = cand.filter(l => String(l["Descricao"] ?? "").toLowerCase().includes(t));
|
|
@@ -1044,11 +1064,11 @@ async function localizarLancamento(crit) {
|
|
|
1044
1064
|
throw new Error(`Nenhum lançamento encontrado em ${crit.data} para "${crit.nomeConta}" com os critérios informados.`);
|
|
1045
1065
|
}
|
|
1046
1066
|
if (cand.length > 1) {
|
|
1047
|
-
const lista = cand.slice(0,
|
|
1048
|
-
throw new Error(`${cand.length} lançamentos batem com os critérios. Refine valor/descrição. Encontrados: ${lista}`);
|
|
1067
|
+
const lista = cand.slice(0, 6).map(l => `${Number(l["Valor"]).toFixed(2)} ${String(l["__moeda"] ?? "")} — ${String(l["Descricao"] ?? "")} [${String(l["NomeConta"] ?? "")}]`).join("; ");
|
|
1068
|
+
throw new Error(`${cand.length} lançamentos batem com os critérios. Refine valor/descrição/moeda. Encontrados: ${lista}`);
|
|
1049
1069
|
}
|
|
1050
1070
|
const m = cand[0];
|
|
1051
|
-
return { id: String(m["Id"]), descricao: String(m["Descricao"] ?? ""), valor: Number(m["Valor"]) };
|
|
1071
|
+
return { id: String(m["Id"]), descricao: String(m["Descricao"] ?? ""), valor: Number(m["Valor"]), conta: String(m["NomeConta"] ?? ""), moeda: m["__moeda"] ? String(m["__moeda"]) : undefined };
|
|
1052
1072
|
}
|
|
1053
1073
|
export async function editarLancamento(opts) {
|
|
1054
1074
|
const alvo = await localizarLancamento(opts);
|
|
@@ -1896,6 +1916,11 @@ async function buscarTitulos(grupo, opts) {
|
|
|
1896
1916
|
const cobrancaTipos = new Set(TIPOS_TITULO.cobranca);
|
|
1897
1917
|
const { contas } = await fetchContas();
|
|
1898
1918
|
const me = await obterUsuarioLogado();
|
|
1919
|
+
const mapaMoedas = await obterMapaMoedas();
|
|
1920
|
+
const moedaDaConta = (c) => {
|
|
1921
|
+
const m = mapaMoedas.get(String(c["IdMoeda"] ?? ""));
|
|
1922
|
+
return (m?.sigla || m?.nome || String(c["NomeMoeda"] ?? "") || "?").trim();
|
|
1923
|
+
};
|
|
1899
1924
|
const alvo = contas
|
|
1900
1925
|
.filter(isContaAtiva)
|
|
1901
1926
|
.filter(c => permissaoDaConta(c, me) !== "Negar")
|
|
@@ -1903,12 +1928,13 @@ async function buscarTitulos(grupo, opts) {
|
|
|
1903
1928
|
const todas = await mapWithConcurrency(alvo, 8, async (c) => {
|
|
1904
1929
|
const id = String(c["Id"] ?? c["IdConta"] ?? "");
|
|
1905
1930
|
const nome = String(c["Nome"] ?? c["NomeConta"] ?? "");
|
|
1931
|
+
const moeda = moedaDaConta(c);
|
|
1906
1932
|
const emCob = cobrancaTipos.has(String(c["NomeTipoConta"] ?? "").toLowerCase().trim());
|
|
1907
1933
|
if (!id)
|
|
1908
1934
|
return [];
|
|
1909
1935
|
try {
|
|
1910
1936
|
const lancs = await fetchLancamentosWebDeUmaConta(id, dataInicio, dataFim);
|
|
1911
|
-
return lancs.map(l => ({ l, nome, emCob }));
|
|
1937
|
+
return lancs.map(l => ({ l, nome, moeda, emCob }));
|
|
1912
1938
|
}
|
|
1913
1939
|
catch {
|
|
1914
1940
|
return [];
|
|
@@ -1917,7 +1943,7 @@ async function buscarTitulos(grupo, opts) {
|
|
|
1917
1943
|
const hojeStr = isoDate(new Date());
|
|
1918
1944
|
let titulos = todas.flat()
|
|
1919
1945
|
.filter(({ l }) => !(l["IdLancamentoTransferencia"] ?? l["Transferencia"]))
|
|
1920
|
-
.map(({ l, nome, emCob }) => {
|
|
1946
|
+
.map(({ l, nome, moeda, emCob }) => {
|
|
1921
1947
|
const s = slimLancamento(l);
|
|
1922
1948
|
if (!s.conta)
|
|
1923
1949
|
s.conta = nome;
|
|
@@ -1927,6 +1953,7 @@ async function buscarTitulos(grupo, opts) {
|
|
|
1927
1953
|
return {
|
|
1928
1954
|
vencimento: venc,
|
|
1929
1955
|
valor: s.valor,
|
|
1956
|
+
moeda,
|
|
1930
1957
|
contraparte: s.descricao,
|
|
1931
1958
|
conta: s.conta,
|
|
1932
1959
|
em_cobranca: emCob,
|
|
@@ -1938,6 +1965,10 @@ async function buscarTitulos(grupo, opts) {
|
|
|
1938
1965
|
observacoes: s.observacoes,
|
|
1939
1966
|
};
|
|
1940
1967
|
});
|
|
1968
|
+
if (opts.moeda?.trim()) {
|
|
1969
|
+
const tm = normalizar(opts.moeda);
|
|
1970
|
+
titulos = titulos.filter(t => normalizar(t.moeda).includes(tm));
|
|
1971
|
+
}
|
|
1941
1972
|
if (opts.apenasCobranca)
|
|
1942
1973
|
titulos = titulos.filter(t => t.em_cobranca);
|
|
1943
1974
|
if (opts.busca) {
|
|
@@ -1947,19 +1978,23 @@ async function buscarTitulos(grupo, opts) {
|
|
|
1947
1978
|
if (opts.apenasVencidos)
|
|
1948
1979
|
titulos = titulos.filter(x => x.vencido);
|
|
1949
1980
|
titulos.sort((a, b) => (a.vencimento < b.vencimento ? -1 : a.vencimento > b.vencimento ? 1 : 0));
|
|
1950
|
-
|
|
1981
|
+
// Totais SEMPRE separados por moeda (nunca somar moedas diferentes)
|
|
1982
|
+
const mapMoeda = new Map();
|
|
1951
1983
|
const mapCp = new Map();
|
|
1952
1984
|
for (const t of titulos) {
|
|
1953
1985
|
const v = Math.abs(t.valor);
|
|
1954
|
-
|
|
1986
|
+
const tm = mapMoeda.get(t.moeda) ?? { moeda: t.moeda, quantidade: 0, valor_total: 0, valor_vencido: 0, valor_a_vencer: 0, em_cobranca: 0 };
|
|
1987
|
+
tm.quantidade += 1;
|
|
1988
|
+
tm.valor_total += v;
|
|
1955
1989
|
if (t.vencido)
|
|
1956
|
-
|
|
1990
|
+
tm.valor_vencido += v;
|
|
1957
1991
|
else
|
|
1958
|
-
|
|
1992
|
+
tm.valor_a_vencer += v;
|
|
1959
1993
|
if (t.em_cobranca)
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
const
|
|
1994
|
+
tm.em_cobranca += v;
|
|
1995
|
+
mapMoeda.set(t.moeda, tm);
|
|
1996
|
+
const key = `${t.contraparte || "(sem descrição)"}|${t.moeda}`;
|
|
1997
|
+
const g = mapCp.get(key) ?? { contraparte: t.contraparte || "(sem descrição)", moeda: t.moeda, valor: 0, quantidade: 0, vencido: 0 };
|
|
1963
1998
|
g.valor += v;
|
|
1964
1999
|
g.quantidade += 1;
|
|
1965
2000
|
if (t.vencido)
|
|
@@ -1968,38 +2003,103 @@ async function buscarTitulos(grupo, opts) {
|
|
|
1968
2003
|
}
|
|
1969
2004
|
const inicio = (pagina - 1) * limite;
|
|
1970
2005
|
const pageItems = titulos.slice(inicio, inicio + limite);
|
|
2006
|
+
const totais_por_moeda = [...mapMoeda.values()]
|
|
2007
|
+
.sort((a, b) => b.valor_total - a.valor_total)
|
|
2008
|
+
.map(t => ({ moeda: t.moeda, quantidade: t.quantidade, valor_total: round2(t.valor_total), valor_vencido: round2(t.valor_vencido), valor_a_vencer: round2(t.valor_a_vencer), em_cobranca: round2(t.em_cobranca) }));
|
|
1971
2009
|
const por_contraparte = [...mapCp.values()]
|
|
1972
2010
|
.sort((a, b) => b.valor - a.valor).slice(0, 50)
|
|
1973
|
-
.map(g => ({ contraparte: g.contraparte, valor: round2(g.valor), quantidade: g.quantidade, vencido: round2(g.vencido) }));
|
|
2011
|
+
.map(g => ({ contraparte: g.contraparte, moeda: g.moeda, valor: round2(g.valor), quantidade: g.quantidade, vencido: round2(g.vencido) }));
|
|
1974
2012
|
return {
|
|
1975
2013
|
titulos: pageItems,
|
|
1976
2014
|
paginacao: { pagina, limite, total: titulos.length, paginas: Math.ceil(titulos.length / limite) || 1, tem_mais: inicio + limite < titulos.length },
|
|
1977
2015
|
periodo: { inicio: dataInicio, fim: dataFim },
|
|
1978
|
-
|
|
1979
|
-
quantidade: titulos.length,
|
|
1980
|
-
valor_total: round2(valorTotal),
|
|
1981
|
-
valor_vencido: round2(valorVencido),
|
|
1982
|
-
valor_a_vencer: round2(valorAVencer),
|
|
1983
|
-
em_cobranca: round2(valorCobranca),
|
|
1984
|
-
},
|
|
2016
|
+
totais_por_moeda,
|
|
1985
2017
|
por_contraparte,
|
|
1986
2018
|
};
|
|
1987
2019
|
}
|
|
1988
2020
|
export const buscarContasAPagar = (o = {}) => buscarTitulos("pagar", o);
|
|
1989
2021
|
export const buscarContasAReceber = (o = {}) => buscarTitulos("receber", o);
|
|
2022
|
+
// ── Despesas / Receitas Periódicas (camada de ESTIMATIVA) ────────────────────
|
|
2023
|
+
// Modelo recorrente que o usuário cadastra nas telas "Despesas Periódicas" /
|
|
2024
|
+
// "Receitas Periódicas". É a ESTIMATIVA mensal (valor previsto, dia do mês),
|
|
2025
|
+
// que depois é programada em Contas a Pagar/Receber e, ao pagar/receber, vira
|
|
2026
|
+
// lançamento real na conta corrente. Endpoint: POST /api/LancamentoPeriodico/Filtrar
|
|
2027
|
+
// {IdConta} — um conjunto por conta-título (a pagar/receber), por moeda.
|
|
2028
|
+
const PERIODICIDADE_LABEL = { 0: "Mensal", 1: "Bimestral", 2: "Trimestral", 3: "Semestral", 4: "Anual" };
|
|
2029
|
+
async function buscarPeriodicos(grupo, opts = {}) {
|
|
2030
|
+
log("buscar_periodicos", grupo, opts.busca ?? "", opts.moeda ?? "");
|
|
2031
|
+
const { contas } = await fetchContas();
|
|
2032
|
+
const me = await obterUsuarioLogado();
|
|
2033
|
+
const mapaMoedas = await obterMapaMoedas();
|
|
2034
|
+
const moedaDaConta = (c) => {
|
|
2035
|
+
const m = mapaMoedas.get(String(c["IdMoeda"] ?? ""));
|
|
2036
|
+
return (m?.sigla || m?.nome || String(c["NomeMoeda"] ?? "") || "?").trim();
|
|
2037
|
+
};
|
|
2038
|
+
const tipoAlvo = grupo === "pagar" ? "conta a pagar" : "conta a receber";
|
|
2039
|
+
const alvo = contas
|
|
2040
|
+
.filter(isContaAtiva)
|
|
2041
|
+
.filter(c => permissaoDaConta(c, me) !== "Negar")
|
|
2042
|
+
.filter(c => String(c["NomeTipoConta"] ?? "").toLowerCase().trim() === tipoAlvo);
|
|
2043
|
+
const todas = await mapWithConcurrency(alvo, 6, async (c) => {
|
|
2044
|
+
const id = String(c["Id"] ?? "");
|
|
2045
|
+
const moeda = moedaDaConta(c);
|
|
2046
|
+
if (!id)
|
|
2047
|
+
return [];
|
|
2048
|
+
try {
|
|
2049
|
+
const r = await apiPost("/api/LancamentoPeriodico/Filtrar", { IdConta: id });
|
|
2050
|
+
const arr = (Array.isArray(r) ? r : Object.values(r).find(v => Array.isArray(v)) ?? []);
|
|
2051
|
+
return arr.map(p => ({ p, moeda }));
|
|
2052
|
+
}
|
|
2053
|
+
catch {
|
|
2054
|
+
return [];
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
let periodicos = todas.flat().map(({ p, moeda }) => ({
|
|
2058
|
+
descricao: String(p["Descricao"] ?? "").trim(),
|
|
2059
|
+
valor_estimado: Number(p["Valor"] ?? 0),
|
|
2060
|
+
moeda,
|
|
2061
|
+
dia: Number(p["Dia"] ?? 0),
|
|
2062
|
+
periodicidade: PERIODICIDADE_LABEL[Number(p["Periodicidade"] ?? 0)] ?? `Código ${p["Periodicidade"]}`,
|
|
2063
|
+
plano_de_contas: p["NomePlanoDeContas"] ? String(p["NomePlanoDeContas"]).trim() : undefined,
|
|
2064
|
+
centro_de_custo: p["NomeCentroDeCustos"] ? String(p["NomeCentroDeCustos"]).trim() : undefined,
|
|
2065
|
+
variacao_tolerada_pct: p["PercentualDiferencaDeVariacao"] != null ? Number(p["PercentualDiferencaDeVariacao"]) : undefined,
|
|
2066
|
+
previsoes_associadas: Number(p["QuantidadeDeLancamentosDePrevisaoAssociados"] ?? 0),
|
|
2067
|
+
observacoes: p["Observacoes"] ? String(p["Observacoes"]).trim() : undefined,
|
|
2068
|
+
}));
|
|
2069
|
+
if (opts.moeda?.trim()) {
|
|
2070
|
+
const tm = normalizar(opts.moeda);
|
|
2071
|
+
periodicos = periodicos.filter(x => normalizar(x.moeda).includes(tm));
|
|
2072
|
+
}
|
|
2073
|
+
if (opts.busca?.trim()) {
|
|
2074
|
+
const t = normalizar(opts.busca);
|
|
2075
|
+
periodicos = periodicos.filter(x => normalizar(x.descricao).includes(t));
|
|
2076
|
+
}
|
|
2077
|
+
periodicos.sort((a, b) => a.dia - b.dia || a.descricao.localeCompare(b.descricao, "pt-BR"));
|
|
2078
|
+
const mapMoeda = new Map();
|
|
2079
|
+
for (const p of periodicos) {
|
|
2080
|
+
const g = mapMoeda.get(p.moeda) ?? { moeda: p.moeda, quantidade: 0, valor: 0 };
|
|
2081
|
+
g.quantidade += 1;
|
|
2082
|
+
g.valor += Math.abs(p.valor_estimado);
|
|
2083
|
+
mapMoeda.set(p.moeda, g);
|
|
2084
|
+
}
|
|
2085
|
+
return {
|
|
2086
|
+
periodicos,
|
|
2087
|
+
estimativa_mensal_por_moeda: [...mapMoeda.values()].sort((a, b) => b.valor - a.valor).map(g => ({ moeda: g.moeda, quantidade: g.quantidade, valor: round2(g.valor) })),
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
export const buscarDespesasPeriodicas = (o = {}) => buscarPeriodicos("pagar", o);
|
|
2091
|
+
export const buscarReceitasPeriodicas = (o = {}) => buscarPeriodicos("receber", o);
|
|
1990
2092
|
/** Lista os clientes da carteira: pessoas com títulos a receber (a partir das contas a receber/cobrança). */
|
|
1991
2093
|
export async function listarClientes(opts = {}) {
|
|
1992
2094
|
log("listar_clientes", opts.busca ?? "");
|
|
1993
2095
|
const pagina = Math.max(1, opts.pagina ?? 1);
|
|
1994
2096
|
const limite = Math.min(200, Math.max(1, opts.limite ?? 50));
|
|
1995
2097
|
// Base: títulos a receber (inclui em cobrança) — a contraparte é o cliente.
|
|
1996
|
-
const recebiveis = await buscarTitulos("receber", { incluirCobranca: true, limite: 200, pagina: 1 });
|
|
1997
|
-
// Reagrupa por contraparte
|
|
2098
|
+
const recebiveis = await buscarTitulos("receber", { incluirCobranca: true, moeda: opts.moeda, limite: 200, pagina: 1 });
|
|
2099
|
+
// Reagrupa por contraparte+moeda (nunca somar moedas diferentes).
|
|
1998
2100
|
const mapa = new Map();
|
|
1999
2101
|
for (const g of recebiveis.por_contraparte ?? []) {
|
|
2000
|
-
|
|
2001
|
-
// tipo: tenta achar a pessoa no cadastro para classificar PF/PJ
|
|
2002
|
-
mapa.set(nome, { nome, tipo: "PJ", titulos_a_receber: g.quantidade, valor_a_receber: g.valor, valor_vencido: g.vencido });
|
|
2102
|
+
mapa.set(`${g.contraparte}|${g.moeda}`, { nome: g.contraparte, tipo: "PJ", moeda: g.moeda, titulos_a_receber: g.quantidade, valor_a_receber: g.valor, valor_vencido: g.vencido });
|
|
2003
2103
|
}
|
|
2004
2104
|
// Classifica PF/PJ consultando o cadastro de pessoas (lista completa cacheada).
|
|
2005
2105
|
try {
|
|
@@ -2018,11 +2118,14 @@ export async function listarClientes(opts = {}) {
|
|
|
2018
2118
|
clientes = clientes.filter(c => normalizar(c.nome).includes(t));
|
|
2019
2119
|
}
|
|
2020
2120
|
clientes.sort((a, b) => b.valor_a_receber - a.valor_a_receber);
|
|
2121
|
+
const totMoeda = new Map();
|
|
2122
|
+
for (const c of clientes)
|
|
2123
|
+
totMoeda.set(c.moeda, (totMoeda.get(c.moeda) ?? 0) + c.valor_a_receber);
|
|
2021
2124
|
const inicio = (pagina - 1) * limite;
|
|
2022
2125
|
return {
|
|
2023
2126
|
clientes: clientes.slice(inicio, inicio + limite),
|
|
2024
2127
|
paginacao: { pagina, limite, total: clientes.length, paginas: Math.ceil(clientes.length / limite) || 1, tem_mais: inicio + limite < clientes.length },
|
|
2025
|
-
|
|
2128
|
+
total_a_receber_por_moeda: [...totMoeda.entries()].map(([moeda, valor]) => ({ moeda, valor: round2(valor) })),
|
|
2026
2129
|
};
|
|
2027
2130
|
}
|
|
2028
2131
|
/**
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { enterDefaultCtx } from "./context.js";
|
|
|
11
11
|
import { openLoginUI } from "./login-server.js";
|
|
12
12
|
// Modo stdio: ativa um contexto único global no startup. Multi-tenancy só no modo HTTP.
|
|
13
13
|
enterDefaultCtx();
|
|
14
|
-
const server = new Server({ name: "next-finance-mcp", version: "0.9.
|
|
14
|
+
const server = new Server({ name: "next-finance-mcp", version: "0.9.41" }, { capabilities: { tools: {} } });
|
|
15
15
|
// ── Ferramentas ─────────────────────────────────────────────────────────────
|
|
16
16
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
17
17
|
tools: [
|
|
@@ -136,15 +136,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
136
136
|
{
|
|
137
137
|
name: "buscar_contas_a_pagar",
|
|
138
138
|
description: "Lista os TÍTULOS A PAGAR (contas a pagar) da carteira — agrega TODAS as contas do tipo 'Conta a Pagar'. " +
|
|
139
|
-
"Cada título traz: vencimento, valor, fornecedor (campo 'contraparte'), conta, se está vencido e os dias " +
|
|
140
|
-
"de atraso/para vencer. Retorna
|
|
141
|
-
"Use apenas_vencidos=true para
|
|
139
|
+
"Cada título traz: vencimento, valor, moeda, fornecedor (campo 'contraparte'), conta, se está vencido e os dias " +
|
|
140
|
+
"de atraso/para vencer. Retorna totais_por_moeda (nunca soma moedas diferentes) e agregação por fornecedor. " +
|
|
141
|
+
"Use moeda='BRL' para focar no real; apenas_vencidos=true para só o que está em atraso. Janela padrão: 1 ano atrás a 1 ano à frente.",
|
|
142
142
|
inputSchema: {
|
|
143
143
|
type: "object",
|
|
144
144
|
properties: {
|
|
145
145
|
data_inicio: { type: "string", description: "Data início do vencimento YYYY-MM-DD (padrão: 1 ano atrás)" },
|
|
146
146
|
data_fim: { type: "string", description: "Data fim do vencimento YYYY-MM-DD (padrão: 1 ano à frente)" },
|
|
147
147
|
busca: { type: "string", description: "Filtra pelo nome do fornecedor (contraparte)" },
|
|
148
|
+
moeda: { type: "string", description: "Filtra por moeda (ex: 'BRL'). Sem isso, retorna todas — e os totais vêm separados por moeda (nunca somados)." },
|
|
148
149
|
apenas_vencidos: { type: "boolean", description: "Se true, retorna só títulos vencidos (em atraso)" },
|
|
149
150
|
pagina: { type: "number", description: "Página (começa em 1, padrão: 1)" },
|
|
150
151
|
limite: { type: "number", description: "Títulos por página (padrão: 50, máximo: 200)" },
|
|
@@ -155,15 +156,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
155
156
|
name: "buscar_contas_a_receber",
|
|
156
157
|
description: "Lista os TÍTULOS A RECEBER da carteira — agrega TODAS as contas do tipo 'Conta a Receber' e, por padrão, " +
|
|
157
158
|
"também as do tipo 'Contas em Cobrança' (cada título tem o flag em_cobranca). Cada título traz: vencimento, " +
|
|
158
|
-
"valor, cliente (campo 'contraparte'), conta, se está vencido e os dias de atraso/para vencer. Retorna
|
|
159
|
-
"(
|
|
160
|
-
"para
|
|
159
|
+
"valor, moeda, cliente (campo 'contraparte'), conta, se está vencido e os dias de atraso/para vencer. Retorna " +
|
|
160
|
+
"totais_por_moeda (nunca soma moedas diferentes) e agregação por cliente. Use moeda='BRL' para focar no real; " +
|
|
161
|
+
"apenas_cobranca=true para SÓ os recebíveis em cobrança; apenas_vencidos=true para só os vencidos. Janela padrão: 1 ano atrás a 1 ano à frente.",
|
|
161
162
|
inputSchema: {
|
|
162
163
|
type: "object",
|
|
163
164
|
properties: {
|
|
164
165
|
data_inicio: { type: "string", description: "Data início do vencimento YYYY-MM-DD (padrão: 1 ano atrás)" },
|
|
165
166
|
data_fim: { type: "string", description: "Data fim do vencimento YYYY-MM-DD (padrão: 1 ano à frente)" },
|
|
166
167
|
busca: { type: "string", description: "Filtra pelo nome do cliente (contraparte)" },
|
|
168
|
+
moeda: { type: "string", description: "Filtra por moeda (ex: 'BRL'). Sem isso, retorna todas — e os totais vêm separados por moeda (nunca somados)." },
|
|
167
169
|
apenas_vencidos: { type: "boolean", description: "Se true, retorna só títulos vencidos (em atraso)" },
|
|
168
170
|
apenas_cobranca: { type: "boolean", description: "Se true, retorna SÓ os títulos em cobrança" },
|
|
169
171
|
incluir_cobranca: { type: "boolean", description: "Inclui as contas 'em Cobrança' na agregação (padrão: true)" },
|
|
@@ -172,6 +174,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
172
174
|
},
|
|
173
175
|
},
|
|
174
176
|
},
|
|
177
|
+
{
|
|
178
|
+
name: "buscar_despesas_periodicas",
|
|
179
|
+
description: "Lista as DESPESAS PERIÓDICAS (estimativas recorrentes) da carteira — a camada de PREVISÃO que o usuário " +
|
|
180
|
+
"cadastra na tela 'Despesas Periódicas' e que depois é programada em Contas a Pagar. Cada item traz: descrição, " +
|
|
181
|
+
"valor_estimado, moeda, dia do mês, periodicidade (Mensal/Anual…), plano de contas, centro de custo, " +
|
|
182
|
+
"variacao_tolerada_pct e previsoes_associadas. Retorna a estimativa_mensal_por_moeda (orçamento mensal). " +
|
|
183
|
+
"Use moeda='BRL' para focar no real. Distinta de buscar_contas_a_pagar (que traz os títulos já programados/reais).",
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
busca: { type: "string", description: "Filtra pela descrição (ex: 'Energisa')" },
|
|
188
|
+
moeda: { type: "string", description: "Filtra por moeda (ex: 'BRL')" },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "buscar_receitas_periodicas",
|
|
194
|
+
description: "Lista as RECEITAS PERIÓDICAS (estimativas recorrentes) da carteira — a camada de PREVISÃO da tela " +
|
|
195
|
+
"'Receitas Periódicas', depois programada em Contas a Receber. Cada item traz: descrição, valor_estimado, " +
|
|
196
|
+
"moeda, dia do mês, periodicidade, plano de contas, centro de custo, variacao_tolerada_pct e previsoes_associadas. " +
|
|
197
|
+
"Retorna a estimativa_mensal_por_moeda. Use moeda='BRL' para focar no real. " +
|
|
198
|
+
"Distinta de buscar_contas_a_receber (títulos já programados/reais).",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
busca: { type: "string", description: "Filtra pela descrição (ex: 'Aluguel')" },
|
|
203
|
+
moeda: { type: "string", description: "Filtra por moeda (ex: 'BRL')" },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
175
207
|
{
|
|
176
208
|
name: "listar_clientes",
|
|
177
209
|
description: "Lista os CLIENTES da carteira (quem tem títulos a receber), ordenados pelo valor a receber. " +
|
|
@@ -183,6 +215,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
183
215
|
type: "object",
|
|
184
216
|
properties: {
|
|
185
217
|
busca: { type: "string", description: "Filtra pelo nome do cliente" },
|
|
218
|
+
moeda: { type: "string", description: "Filtra por moeda dos recebíveis (ex: 'BRL'). O total a receber vem separado por moeda." },
|
|
186
219
|
pagina: { type: "number", description: "Página (começa em 1, padrão: 1)" },
|
|
187
220
|
limite: { type: "number", description: "Clientes por página (padrão: 50, máximo: 200)" },
|
|
188
221
|
},
|
|
@@ -234,8 +267,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
234
267
|
},
|
|
235
268
|
{
|
|
236
269
|
name: "editar_lancamento",
|
|
237
|
-
description: "Edita um lançamento existente. Localiza pelo
|
|
238
|
-
"
|
|
270
|
+
description: "Edita um lançamento existente. Localiza pelo nome_conta + data + valor/descrição (varre TODAS as contas " +
|
|
271
|
+
"homônimas, inclusive em moedas diferentes — ex: as várias 'Conta a Pagar') e aplica as alterações. " +
|
|
272
|
+
"Se mais de um lançamento bater, a tool lista as opções para você refinar (use 'moeda' para desambiguar).",
|
|
239
273
|
inputSchema: {
|
|
240
274
|
type: "object",
|
|
241
275
|
properties: {
|
|
@@ -244,6 +278,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
244
278
|
data: { type: "string", description: "Data do lançamento YYYY-MM-DD (para localizar)" },
|
|
245
279
|
valor: { type: "number", description: "Valor original (para localizar, opcional)" },
|
|
246
280
|
descricao: { type: "string", description: "Descrição original (substring, para localizar, opcional)" },
|
|
281
|
+
moeda: { type: "string", description: "Moeda da conta (ex: 'BRL') — desambigua contas homônimas em moedas diferentes" },
|
|
247
282
|
// Campos a alterar (qualquer um, todos opcionais)
|
|
248
283
|
nova_descricao: { type: "string", description: "Nova descrição" },
|
|
249
284
|
novo_valor: { type: "number", description: "Novo valor (negativo = despesa, positivo = receita)" },
|
|
@@ -271,6 +306,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
271
306
|
data: { type: "string", description: "Data do lançamento YYYY-MM-DD" },
|
|
272
307
|
valor: { type: "number", description: "Valor (opcional, ajuda a desambiguar)" },
|
|
273
308
|
descricao: { type: "string", description: "Descrição (substring, opcional)" },
|
|
309
|
+
moeda: { type: "string", description: "Moeda da conta (ex: 'BRL') — desambigua contas homônimas em moedas diferentes" },
|
|
274
310
|
},
|
|
275
311
|
required: ["nome_conta", "data"],
|
|
276
312
|
},
|
|
@@ -787,6 +823,7 @@ async function handleTool(name, args) {
|
|
|
787
823
|
dataInicio: args["data_inicio"],
|
|
788
824
|
dataFim: args["data_fim"],
|
|
789
825
|
busca: args["busca"],
|
|
826
|
+
moeda: args["moeda"],
|
|
790
827
|
apenasVencidos: args["apenas_vencidos"],
|
|
791
828
|
pagina: args["pagina"],
|
|
792
829
|
limite: args["limite"],
|
|
@@ -803,6 +840,7 @@ async function handleTool(name, args) {
|
|
|
803
840
|
dataInicio: args["data_inicio"],
|
|
804
841
|
dataFim: args["data_fim"],
|
|
805
842
|
busca: args["busca"],
|
|
843
|
+
moeda: args["moeda"],
|
|
806
844
|
apenasVencidos: args["apenas_vencidos"],
|
|
807
845
|
apenasCobranca: args["apenas_cobranca"],
|
|
808
846
|
incluirCobranca: args["incluir_cobranca"],
|
|
@@ -810,6 +848,30 @@ async function handleTool(name, args) {
|
|
|
810
848
|
limite: args["limite"],
|
|
811
849
|
}));
|
|
812
850
|
}
|
|
851
|
+
case "buscar_despesas_periodicas": {
|
|
852
|
+
const authErr = requireAuth();
|
|
853
|
+
if (authErr)
|
|
854
|
+
return authErr;
|
|
855
|
+
const walErr = requireWallet();
|
|
856
|
+
if (walErr)
|
|
857
|
+
return walErr;
|
|
858
|
+
return ok(await client.buscarDespesasPeriodicas({
|
|
859
|
+
busca: args["busca"],
|
|
860
|
+
moeda: args["moeda"],
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
case "buscar_receitas_periodicas": {
|
|
864
|
+
const authErr = requireAuth();
|
|
865
|
+
if (authErr)
|
|
866
|
+
return authErr;
|
|
867
|
+
const walErr = requireWallet();
|
|
868
|
+
if (walErr)
|
|
869
|
+
return walErr;
|
|
870
|
+
return ok(await client.buscarReceitasPeriodicas({
|
|
871
|
+
busca: args["busca"],
|
|
872
|
+
moeda: args["moeda"],
|
|
873
|
+
}));
|
|
874
|
+
}
|
|
813
875
|
case "listar_clientes": {
|
|
814
876
|
const authErr = requireAuth();
|
|
815
877
|
if (authErr)
|
|
@@ -819,6 +881,7 @@ async function handleTool(name, args) {
|
|
|
819
881
|
return walErr;
|
|
820
882
|
return ok(await client.listarClientes({
|
|
821
883
|
busca: args["busca"],
|
|
884
|
+
moeda: args["moeda"],
|
|
822
885
|
pagina: args["pagina"],
|
|
823
886
|
limite: args["limite"],
|
|
824
887
|
}));
|
|
@@ -879,6 +942,7 @@ async function handleTool(name, args) {
|
|
|
879
942
|
data: args["data"],
|
|
880
943
|
valor: args["valor"],
|
|
881
944
|
descricao: args["descricao"],
|
|
945
|
+
moeda: args["moeda"],
|
|
882
946
|
nova_descricao: args["nova_descricao"],
|
|
883
947
|
novo_valor: args["novo_valor"],
|
|
884
948
|
nova_data: args["nova_data"],
|
|
@@ -904,6 +968,7 @@ async function handleTool(name, args) {
|
|
|
904
968
|
data: args["data"],
|
|
905
969
|
valor: args["valor"],
|
|
906
970
|
descricao: args["descricao"],
|
|
971
|
+
moeda: args["moeda"],
|
|
907
972
|
}));
|
|
908
973
|
}
|
|
909
974
|
case "permissao_conta": {
|
|
@@ -1182,6 +1247,6 @@ async function handleTool(name, args) {
|
|
|
1182
1247
|
async function main() {
|
|
1183
1248
|
const transport = new StdioServerTransport();
|
|
1184
1249
|
await server.connect(transport);
|
|
1185
|
-
client.log(`NEXT Finance MCP server v0.9.
|
|
1250
|
+
client.log(`NEXT Finance MCP server v0.9.41 iniciado. Timeout upstream: ${process.env.NEXT_FINANCE_TIMEOUT_MS ?? "15000"}ms`);
|
|
1186
1251
|
}
|
|
1187
1252
|
main().catch((err) => { console.error("Erro fatal:", err); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-finance-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.41",
|
|
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",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
42
|
+
"docx": "^9.7.1",
|
|
42
43
|
"express": "^5.2.1",
|
|
43
44
|
"undici": "^7.25.0"
|
|
44
45
|
},
|