mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-03-10 04:51:47 +00:00
perf: otimizar dashboard com indexes, cache e consolidação de queries (v1.3.0)
- Adicionar indexes compostos em lancamentos para queries frequentes - Eliminar ~20 JOINs com pagadores via helper cacheado getAdminPagadorId() - Consolidar queries: income-expense-balance (12→1), payment-status (2→1), categories (4→2) - Adicionar cache cross-request via unstable_cache com tag-based invalidation - Limitar scan de métricas a 24 meses - Deduplicar auth session por request via React.cache() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { lancamentos, pagadores } from "@/db/schema";
|
||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||
import { lancamentos } from "@/db/schema";
|
||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/lib/accounts/constants";
|
||||
import { toNumber } from "@/lib/dashboard/common";
|
||||
import { db } from "@/lib/db";
|
||||
import { getAdminPagadorId } from "@/lib/pagadores/get-admin-id";
|
||||
|
||||
export type PaymentStatusCategory = {
|
||||
total: number;
|
||||
@@ -15,106 +16,67 @@ export type PaymentStatusData = {
|
||||
expenses: PaymentStatusCategory;
|
||||
};
|
||||
|
||||
const emptyCategory = (): PaymentStatusCategory => ({
|
||||
total: 0,
|
||||
confirmed: 0,
|
||||
pending: 0,
|
||||
});
|
||||
|
||||
export async function fetchPaymentStatus(
|
||||
userId: string,
|
||||
period: string,
|
||||
): Promise<PaymentStatusData> {
|
||||
// Busca receitas confirmadas e pendentes para o período do pagador admin
|
||||
// Exclui lançamentos de pagamento de fatura (para evitar contagem duplicada)
|
||||
const incomeResult = await db
|
||||
const adminPagadorId = await getAdminPagadorId(userId);
|
||||
if (!adminPagadorId) {
|
||||
return { income: emptyCategory(), expenses: emptyCategory() };
|
||||
}
|
||||
|
||||
// Single query: GROUP BY transactionType instead of 2 separate queries
|
||||
const rows = await db
|
||||
.select({
|
||||
transactionType: lancamentos.transactionType,
|
||||
confirmed: sql<number>`
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.isSettled} = true then ${lancamentos.amount}
|
||||
else 0
|
||||
end
|
||||
),
|
||||
0
|
||||
)
|
||||
`,
|
||||
coalesce(
|
||||
sum(case when ${lancamentos.isSettled} = true then ${lancamentos.amount} else 0 end),
|
||||
0
|
||||
)
|
||||
`,
|
||||
pending: sql<number>`
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.isSettled} = false or ${lancamentos.isSettled} is null then ${lancamentos.amount}
|
||||
else 0
|
||||
end
|
||||
),
|
||||
0
|
||||
)
|
||||
`,
|
||||
coalesce(
|
||||
sum(case when ${lancamentos.isSettled} = false or ${lancamentos.isSettled} is null then ${lancamentos.amount} else 0 end),
|
||||
0
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(lancamentos.transactionType, "Receita"),
|
||||
eq(pagadores.role, "admin"),
|
||||
eq(lancamentos.pagadorId, adminPagadorId),
|
||||
inArray(lancamentos.transactionType, ["Receita", "Despesa"]),
|
||||
sql`(${lancamentos.note} IS NULL OR ${lancamentos.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`})`,
|
||||
),
|
||||
);
|
||||
)
|
||||
.groupBy(lancamentos.transactionType);
|
||||
|
||||
// Busca despesas confirmadas e pendentes para o período do pagador admin
|
||||
// Exclui lançamentos de pagamento de fatura (para evitar contagem duplicada)
|
||||
const expensesResult = await db
|
||||
.select({
|
||||
confirmed: sql<number>`
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.isSettled} = true then ${lancamentos.amount}
|
||||
else 0
|
||||
end
|
||||
),
|
||||
0
|
||||
)
|
||||
`,
|
||||
pending: sql<number>`
|
||||
coalesce(
|
||||
sum(
|
||||
case
|
||||
when ${lancamentos.isSettled} = false or ${lancamentos.isSettled} is null then ${lancamentos.amount}
|
||||
else 0
|
||||
end
|
||||
),
|
||||
0
|
||||
)
|
||||
`,
|
||||
})
|
||||
.from(lancamentos)
|
||||
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
|
||||
.where(
|
||||
and(
|
||||
eq(lancamentos.userId, userId),
|
||||
eq(lancamentos.period, period),
|
||||
eq(lancamentos.transactionType, "Despesa"),
|
||||
eq(pagadores.role, "admin"),
|
||||
sql`(${lancamentos.note} IS NULL OR ${lancamentos.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`})`,
|
||||
),
|
||||
);
|
||||
const result = { income: emptyCategory(), expenses: emptyCategory() };
|
||||
|
||||
const incomeData = incomeResult[0] ?? { confirmed: 0, pending: 0 };
|
||||
const confirmedIncome = toNumber(incomeData.confirmed);
|
||||
const pendingIncome = toNumber(incomeData.pending);
|
||||
for (const row of rows) {
|
||||
const confirmed = toNumber(row.confirmed);
|
||||
const pending = toNumber(row.pending);
|
||||
const category = {
|
||||
total: confirmed + pending,
|
||||
confirmed,
|
||||
pending,
|
||||
};
|
||||
|
||||
const expensesData = expensesResult[0] ?? { confirmed: 0, pending: 0 };
|
||||
const confirmedExpenses = toNumber(expensesData.confirmed);
|
||||
const pendingExpenses = toNumber(expensesData.pending);
|
||||
if (row.transactionType === "Receita") {
|
||||
result.income = category;
|
||||
} else if (row.transactionType === "Despesa") {
|
||||
result.expenses = category;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
income: {
|
||||
total: confirmedIncome + pendingIncome,
|
||||
confirmed: confirmedIncome,
|
||||
pending: pendingIncome,
|
||||
},
|
||||
expenses: {
|
||||
total: confirmedExpenses + pendingExpenses,
|
||||
confirmed: confirmedExpenses,
|
||||
pending: pendingExpenses,
|
||||
},
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user