fix(orcamentos): alinhar cálculo de gastos com widget do dashboard

A query de "Gasto até agora" nos orçamentos agora aplica os mesmos
filtros do widget de despesas por categoria do dashboard:
- INNER JOIN com pagadores (exclui lançamentos sem pagador)
- Filtra apenas pagadores com role "admin" (exclui terceiros)
- Exclui notas de faturas automáticas (AUTO_FATURA:*)

Isso corrige a discrepância onde orçamentos mostravam valores
diferentes do widget de despesas para a mesma categoria.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-02-05 12:52:50 +00:00
parent 757626c468
commit 0249226026
2 changed files with 12 additions and 3 deletions

View File

@@ -1,11 +1,14 @@
import { and, asc, eq, inArray, sum } from "drizzle-orm"; import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm";
import { import {
categorias, categorias,
lancamentos, lancamentos,
type Orcamento, type Orcamento,
orcamentos, orcamentos,
pagadores,
} from "@/db/schema"; } from "@/db/schema";
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/lib/accounts/constants";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
const toNumber = (value: string | number | null | undefined) => { const toNumber = (value: string | number | null | undefined) => {
if (typeof value === "number") return value; if (typeof value === "number") return value;
@@ -76,12 +79,18 @@ export async function fetchBudgetsForUser(
totalAmount: sum(lancamentos.amount).as("totalAmount"), totalAmount: sum(lancamentos.amount).as("totalAmount"),
}) })
.from(lancamentos) .from(lancamentos)
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
.where( .where(
and( and(
eq(lancamentos.userId, userId), eq(lancamentos.userId, userId),
eq(lancamentos.period, selectedPeriod), eq(lancamentos.period, selectedPeriod),
eq(lancamentos.transactionType, "Despesa"), eq(lancamentos.transactionType, "Despesa"),
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
inArray(lancamentos.categoriaId, categoryIds), inArray(lancamentos.categoriaId, categoryIds),
or(
isNull(lancamentos.note),
sql`${lancamentos.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
),
), ),
) )
.groupBy(lancamentos.categoriaId); .groupBy(lancamentos.categoriaId);

View File

@@ -76,13 +76,13 @@ export function BudgetCard({
<MoneyValues amount={limit} className="text-foreground" /> <MoneyValues amount={limit} className="text-foreground" />
</div> </div>
<div className="mt-2"> <div>
{exceeded ? ( {exceeded ? (
<div className="text-xs text-red-500"> <div className="text-xs text-red-500">
Excedeu em <MoneyValues amount={difference} /> Excedeu em <MoneyValues amount={difference} />
</div> </div>
) : ( ) : (
<div className="text-sm text-green-600"> <div className="text-xs text-green-600">
Restam <MoneyValues amount={Math.max(limit - spent, 0)} />{" "} Restam <MoneyValues amount={Math.max(limit - spent, 0)} />{" "}
disponíveis. disponíveis.
</div> </div>