fix: exclui transações de contas fora do saldo nos totais por pessoa e orçamentos

Adicionado leftJoin(financialAccounts) + excludeTransactionsFromExcludedAccounts()
em 6 queries de payers/details.ts (totais do mês, histórico, uso de cartões, etc.)
e em fetchBudgetsForUser/fetchCategoryBudgetSummary de budgets/queries.ts.
Contas marcadas como excludeFromBalance (ex: Ajuste de saldo) não entram mais
nos cálculos de gasto, alinhando a tela de Pessoas, Orçamentos e o badge do modal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-05-10 13:51:13 +00:00
parent 467f71493d
commit 7128cc0ae7
2 changed files with 98 additions and 2 deletions

View File

@@ -1,6 +1,12 @@
import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm"; import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm";
import { budgets, categories, transactions } from "@/db/schema"; import {
budgets,
categories,
financialAccounts,
transactions,
} from "@/db/schema";
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants"; import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
import { excludeTransactionsFromExcludedAccounts } from "@/shared/lib/accounts/query-filters";
import { db } from "@/shared/lib/db"; import { db } from "@/shared/lib/db";
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id"; import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
@@ -75,6 +81,10 @@ export async function fetchBudgetsForUser(
totalAmount: sum(transactions.amount).as("totalAmount"), totalAmount: sum(transactions.amount).as("totalAmount"),
}) })
.from(transactions) .from(transactions)
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where( .where(
and( and(
eq(transactions.userId, userId), eq(transactions.userId, userId),
@@ -86,6 +96,7 @@ export async function fetchBudgetsForUser(
isNull(transactions.note), isNull(transactions.note),
sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`, sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
), ),
excludeTransactionsFromExcludedAccounts(),
), ),
) )
.groupBy(transactions.categoryId); .groupBy(transactions.categoryId);
@@ -127,3 +138,57 @@ export async function fetchBudgetsForUser(
return { budgets: budgetList, categoriesOptions }; return { budgets: budgetList, categoriesOptions };
} }
export type CategoryBudgetSummary = {
amount: number;
spent: number;
};
export async function fetchCategoryBudgetSummary(
userId: string,
categoryId: string,
period: string,
): Promise<CategoryBudgetSummary | null> {
const [adminPayerId, budget] = await Promise.all([
getAdminPayerId(userId),
db.query.budgets.findFirst({
columns: { amount: true },
where: and(
eq(budgets.userId, userId),
eq(budgets.categoryId, categoryId),
eq(budgets.period, period),
),
}),
]);
if (!adminPayerId || !budget) return null;
const totals = await db
.select({
totalAmount: sum(transactions.amount).as("totalAmount"),
})
.from(transactions)
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where(
and(
eq(transactions.userId, userId),
eq(transactions.period, period),
eq(transactions.transactionType, "Despesa"),
eq(transactions.payerId, adminPayerId),
eq(transactions.categoryId, categoryId),
or(
isNull(transactions.note),
sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
),
excludeTransactionsFromExcludedAccounts(),
),
);
return {
amount: toNumber(budget.amount),
spent: Math.abs(toNumber(totals[0]?.totalAmount ?? 0)),
};
}

View File

@@ -11,8 +11,9 @@ import {
sql, sql,
sum, sum,
} from "drizzle-orm"; } from "drizzle-orm";
import { cards, transactions } from "@/db/schema"; import { cards, financialAccounts, transactions } from "@/db/schema";
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants"; import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
import { excludeTransactionsFromExcludedAccounts } from "@/shared/lib/accounts/query-filters";
import { db } from "@/shared/lib/db"; import { db } from "@/shared/lib/db";
import { toDateOnlyString } from "@/shared/utils/date"; import { toDateOnlyString } from "@/shared/utils/date";
import { safeToNumber as toNumber } from "@/shared/utils/number"; import { safeToNumber as toNumber } from "@/shared/utils/number";
@@ -96,12 +97,17 @@ export async function fetchPayerMonthlyBreakdown({
totalAmount: sum(transactions.amount).as("total"), totalAmount: sum(transactions.amount).as("total"),
}) })
.from(transactions) .from(transactions)
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where( .where(
and( and(
eq(transactions.userId, userId), eq(transactions.userId, userId),
eq(transactions.payerId, payerId), eq(transactions.payerId, payerId),
eq(transactions.period, period), eq(transactions.period, period),
excludeAutoInvoiceEntries(), excludeAutoInvoiceEntries(),
excludeTransactionsFromExcludedAccounts(),
), ),
) )
.groupBy(transactions.paymentMethod, transactions.transactionType); .groupBy(transactions.paymentMethod, transactions.transactionType);
@@ -155,6 +161,10 @@ export async function fetchPayerHistory({
totalAmount: sum(transactions.amount).as("total"), totalAmount: sum(transactions.amount).as("total"),
}) })
.from(transactions) .from(transactions)
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where( .where(
and( and(
eq(transactions.userId, userId), eq(transactions.userId, userId),
@@ -162,6 +172,7 @@ export async function fetchPayerHistory({
gte(transactions.period, start), gte(transactions.period, start),
lte(transactions.period, end), lte(transactions.period, end),
excludeAutoInvoiceEntries(), excludeAutoInvoiceEntries(),
excludeTransactionsFromExcludedAccounts(),
), ),
) )
.groupBy(transactions.period, transactions.transactionType); .groupBy(transactions.period, transactions.transactionType);
@@ -210,6 +221,10 @@ export async function fetchPayerCardUsage({
}) })
.from(transactions) .from(transactions)
.innerJoin(cards, eq(transactions.cardId, cards.id)) .innerJoin(cards, eq(transactions.cardId, cards.id))
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where( .where(
and( and(
eq(transactions.userId, userId), eq(transactions.userId, userId),
@@ -217,6 +232,7 @@ export async function fetchPayerCardUsage({
eq(transactions.period, period), eq(transactions.period, period),
eq(transactions.paymentMethod, PAYMENT_METHOD_CARD), eq(transactions.paymentMethod, PAYMENT_METHOD_CARD),
excludeAutoInvoiceEntries(), excludeAutoInvoiceEntries(),
excludeTransactionsFromExcludedAccounts(),
), ),
) )
.groupBy(transactions.cardId, cards.name, cards.logo); .groupBy(transactions.cardId, cards.name, cards.logo);
@@ -251,6 +267,10 @@ export async function fetchPayerBoletoStats({
totalCount: sql<number>`count(${transactions.id})`.as("count"), totalCount: sql<number>`count(${transactions.id})`.as("count"),
}) })
.from(transactions) .from(transactions)
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where( .where(
and( and(
eq(transactions.userId, userId), eq(transactions.userId, userId),
@@ -258,6 +278,7 @@ export async function fetchPayerBoletoStats({
eq(transactions.period, period), eq(transactions.period, period),
eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO), eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO),
excludeAutoInvoiceEntries(), excludeAutoInvoiceEntries(),
excludeTransactionsFromExcludedAccounts(),
), ),
) )
.groupBy(transactions.isSettled); .groupBy(transactions.isSettled);
@@ -303,6 +324,10 @@ export async function fetchPayerBoletoItems({
isSettled: transactions.isSettled, isSettled: transactions.isSettled,
}) })
.from(transactions) .from(transactions)
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where( .where(
and( and(
eq(transactions.userId, userId), eq(transactions.userId, userId),
@@ -310,6 +335,7 @@ export async function fetchPayerBoletoItems({
eq(transactions.period, period), eq(transactions.period, period),
eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO), eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO),
excludeAutoInvoiceEntries(), excludeAutoInvoiceEntries(),
excludeTransactionsFromExcludedAccounts(),
), ),
) )
.orderBy(asc(transactions.dueDate)); .orderBy(asc(transactions.dueDate));
@@ -343,6 +369,10 @@ export async function fetchPayerPaymentStatus({
pendingCount: sql<number>`sum(case when (${transactions.isSettled} = false or ${transactions.isSettled} is null) then 1 else 0 end)`, pendingCount: sql<number>`sum(case when (${transactions.isSettled} = false or ${transactions.isSettled} is null) then 1 else 0 end)`,
}) })
.from(transactions) .from(transactions)
.leftJoin(
financialAccounts,
eq(transactions.accountId, financialAccounts.id),
)
.where( .where(
and( and(
eq(transactions.userId, userId), eq(transactions.userId, userId),
@@ -350,6 +380,7 @@ export async function fetchPayerPaymentStatus({
eq(transactions.period, period), eq(transactions.period, period),
eq(transactions.transactionType, DESPESA), eq(transactions.transactionType, DESPESA),
excludeAutoInvoiceEntries(), excludeAutoInvoiceEntries(),
excludeTransactionsFromExcludedAccounts(),
), ),
); );