From 7128cc0ae7bf1219c49bca823afd6b31ea58a224 Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Sun, 10 May 2026 13:51:13 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20exclui=20transa=C3=A7=C3=B5es=20de=20con?= =?UTF-8?q?tas=20fora=20do=20saldo=20nos=20totais=20por=20pessoa=20e=20or?= =?UTF-8?q?=C3=A7amentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/features/budgets/queries.ts | 67 +++++++++++++++++++++++++++++++- src/shared/lib/payers/details.ts | 33 +++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/features/budgets/queries.ts b/src/features/budgets/queries.ts index 33233f6..625d496 100644 --- a/src/features/budgets/queries.ts +++ b/src/features/budgets/queries.ts @@ -1,6 +1,12 @@ 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 { excludeTransactionsFromExcludedAccounts } from "@/shared/lib/accounts/query-filters"; import { db } from "@/shared/lib/db"; import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id"; @@ -75,6 +81,10 @@ export async function fetchBudgetsForUser( totalAmount: sum(transactions.amount).as("totalAmount"), }) .from(transactions) + .leftJoin( + financialAccounts, + eq(transactions.accountId, financialAccounts.id), + ) .where( and( eq(transactions.userId, userId), @@ -86,6 +96,7 @@ export async function fetchBudgetsForUser( isNull(transactions.note), sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`, ), + excludeTransactionsFromExcludedAccounts(), ), ) .groupBy(transactions.categoryId); @@ -127,3 +138,57 @@ export async function fetchBudgetsForUser( return { budgets: budgetList, categoriesOptions }; } + +export type CategoryBudgetSummary = { + amount: number; + spent: number; +}; + +export async function fetchCategoryBudgetSummary( + userId: string, + categoryId: string, + period: string, +): Promise { + 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)), + }; +} diff --git a/src/shared/lib/payers/details.ts b/src/shared/lib/payers/details.ts index 2dc1752..75c71af 100644 --- a/src/shared/lib/payers/details.ts +++ b/src/shared/lib/payers/details.ts @@ -11,8 +11,9 @@ import { sql, sum, } 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 { excludeTransactionsFromExcludedAccounts } from "@/shared/lib/accounts/query-filters"; import { db } from "@/shared/lib/db"; import { toDateOnlyString } from "@/shared/utils/date"; import { safeToNumber as toNumber } from "@/shared/utils/number"; @@ -96,12 +97,17 @@ export async function fetchPayerMonthlyBreakdown({ totalAmount: sum(transactions.amount).as("total"), }) .from(transactions) + .leftJoin( + financialAccounts, + eq(transactions.accountId, financialAccounts.id), + ) .where( and( eq(transactions.userId, userId), eq(transactions.payerId, payerId), eq(transactions.period, period), excludeAutoInvoiceEntries(), + excludeTransactionsFromExcludedAccounts(), ), ) .groupBy(transactions.paymentMethod, transactions.transactionType); @@ -155,6 +161,10 @@ export async function fetchPayerHistory({ totalAmount: sum(transactions.amount).as("total"), }) .from(transactions) + .leftJoin( + financialAccounts, + eq(transactions.accountId, financialAccounts.id), + ) .where( and( eq(transactions.userId, userId), @@ -162,6 +172,7 @@ export async function fetchPayerHistory({ gte(transactions.period, start), lte(transactions.period, end), excludeAutoInvoiceEntries(), + excludeTransactionsFromExcludedAccounts(), ), ) .groupBy(transactions.period, transactions.transactionType); @@ -210,6 +221,10 @@ export async function fetchPayerCardUsage({ }) .from(transactions) .innerJoin(cards, eq(transactions.cardId, cards.id)) + .leftJoin( + financialAccounts, + eq(transactions.accountId, financialAccounts.id), + ) .where( and( eq(transactions.userId, userId), @@ -217,6 +232,7 @@ export async function fetchPayerCardUsage({ eq(transactions.period, period), eq(transactions.paymentMethod, PAYMENT_METHOD_CARD), excludeAutoInvoiceEntries(), + excludeTransactionsFromExcludedAccounts(), ), ) .groupBy(transactions.cardId, cards.name, cards.logo); @@ -251,6 +267,10 @@ export async function fetchPayerBoletoStats({ totalCount: sql`count(${transactions.id})`.as("count"), }) .from(transactions) + .leftJoin( + financialAccounts, + eq(transactions.accountId, financialAccounts.id), + ) .where( and( eq(transactions.userId, userId), @@ -258,6 +278,7 @@ export async function fetchPayerBoletoStats({ eq(transactions.period, period), eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO), excludeAutoInvoiceEntries(), + excludeTransactionsFromExcludedAccounts(), ), ) .groupBy(transactions.isSettled); @@ -303,6 +324,10 @@ export async function fetchPayerBoletoItems({ isSettled: transactions.isSettled, }) .from(transactions) + .leftJoin( + financialAccounts, + eq(transactions.accountId, financialAccounts.id), + ) .where( and( eq(transactions.userId, userId), @@ -310,6 +335,7 @@ export async function fetchPayerBoletoItems({ eq(transactions.period, period), eq(transactions.paymentMethod, PAYMENT_METHOD_BOLETO), excludeAutoInvoiceEntries(), + excludeTransactionsFromExcludedAccounts(), ), ) .orderBy(asc(transactions.dueDate)); @@ -343,6 +369,10 @@ export async function fetchPayerPaymentStatus({ pendingCount: sql`sum(case when (${transactions.isSettled} = false or ${transactions.isSettled} is null) then 1 else 0 end)`, }) .from(transactions) + .leftJoin( + financialAccounts, + eq(transactions.accountId, financialAccounts.id), + ) .where( and( eq(transactions.userId, userId), @@ -350,6 +380,7 @@ export async function fetchPayerPaymentStatus({ eq(transactions.period, period), eq(transactions.transactionType, DESPESA), excludeAutoInvoiceEntries(), + excludeTransactionsFromExcludedAccounts(), ), );