refactor: migrate from ESLint to Biome and extract SQL queries to data.ts

- Replace ESLint with Biome for linting and formatting
- Configure Biome with tabs, double quotes, and organized imports
- Move all SQL/Drizzle queries from page.tsx files to data.ts files
- Create new data.ts files for: ajustes, dashboard, relatorios/categorias
- Update existing data.ts files: extrato, fatura (add lancamentos queries)
- Remove all drizzle-orm imports from page.tsx files
- Update README.md with new tooling info

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-01-27 13:15:37 +00:00
parent 8ffe61c59b
commit a7f63fb77a
442 changed files with 66141 additions and 69292 deletions

View File

@@ -1,162 +1,171 @@
import { lancamentos, pagadores, contas } from "@/db/schema";
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX, INITIAL_BALANCE_NOTE } from "@/lib/accounts/constants";
import {
and,
asc,
eq,
ilike,
isNull,
lte,
ne,
not,
or,
sum,
} from "drizzle-orm";
import { contas, lancamentos, pagadores } from "@/db/schema";
import {
ACCOUNT_AUTO_INVOICE_NOTE_PREFIX,
INITIAL_BALANCE_NOTE,
} from "@/lib/accounts/constants";
import { db } from "@/lib/db";
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
import {
getPreviousPeriod,
buildPeriodRange,
comparePeriods,
} from "@/lib/utils/period";
import { safeToNumber } from "@/lib/utils/number";
import { and, asc, eq, ilike, isNull, lte, not, or, sum, ne } from "drizzle-orm";
import {
buildPeriodRange,
comparePeriods,
getPreviousPeriod,
} from "@/lib/utils/period";
const RECEITA = "Receita";
const DESPESA = "Despesa";
const TRANSFERENCIA = "Transferência";
type MetricPair = {
current: number;
previous: number;
current: number;
previous: number;
};
export type DashboardCardMetrics = {
period: string;
previousPeriod: string;
receitas: MetricPair;
despesas: MetricPair;
balanco: MetricPair;
previsto: MetricPair;
period: string;
previousPeriod: string;
receitas: MetricPair;
despesas: MetricPair;
balanco: MetricPair;
previsto: MetricPair;
};
type PeriodTotals = {
receitas: number;
despesas: number;
balanco: number;
receitas: number;
despesas: number;
balanco: number;
};
const createEmptyTotals = (): PeriodTotals => ({
receitas: 0,
despesas: 0,
balanco: 0,
receitas: 0,
despesas: 0,
balanco: 0,
});
const ensurePeriodTotals = (
store: Map<string, PeriodTotals>,
period: string
store: Map<string, PeriodTotals>,
period: string,
): PeriodTotals => {
if (!store.has(period)) {
store.set(period, createEmptyTotals());
}
const totals = store.get(period);
// This should always exist since we just set it above
if (!totals) {
const emptyTotals = createEmptyTotals();
store.set(period, emptyTotals);
return emptyTotals;
}
return totals;
if (!store.has(period)) {
store.set(period, createEmptyTotals());
}
const totals = store.get(period);
// This should always exist since we just set it above
if (!totals) {
const emptyTotals = createEmptyTotals();
store.set(period, emptyTotals);
return emptyTotals;
}
return totals;
};
// Re-export for backward compatibility
export { getPreviousPeriod };
export async function fetchDashboardCardMetrics(
userId: string,
period: string
userId: string,
period: string,
): Promise<DashboardCardMetrics> {
const previousPeriod = getPreviousPeriod(period);
const previousPeriod = getPreviousPeriod(period);
const rows = await db
.select({
period: lancamentos.period,
transactionType: lancamentos.transactionType,
totalAmount: sum(lancamentos.amount).as("total"),
})
.from(lancamentos)
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
.leftJoin(contas, eq(lancamentos.contaId, contas.id))
.where(
and(
eq(lancamentos.userId, userId),
lte(lancamentos.period, period),
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
ne(lancamentos.transactionType, TRANSFERENCIA),
or(
isNull(lancamentos.note),
not(
ilike(
lancamentos.note,
`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`
)
)
),
// Excluir saldos iniciais se a conta tiver o flag ativo
or(
ne(lancamentos.note, INITIAL_BALANCE_NOTE),
isNull(contas.excludeInitialBalanceFromIncome),
eq(contas.excludeInitialBalanceFromIncome, false)
)
)
)
.groupBy(lancamentos.period, lancamentos.transactionType)
.orderBy(asc(lancamentos.period), asc(lancamentos.transactionType));
const rows = await db
.select({
period: lancamentos.period,
transactionType: lancamentos.transactionType,
totalAmount: sum(lancamentos.amount).as("total"),
})
.from(lancamentos)
.innerJoin(pagadores, eq(lancamentos.pagadorId, pagadores.id))
.leftJoin(contas, eq(lancamentos.contaId, contas.id))
.where(
and(
eq(lancamentos.userId, userId),
lte(lancamentos.period, period),
eq(pagadores.role, PAGADOR_ROLE_ADMIN),
ne(lancamentos.transactionType, TRANSFERENCIA),
or(
isNull(lancamentos.note),
not(ilike(lancamentos.note, `${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`)),
),
// Excluir saldos iniciais se a conta tiver o flag ativo
or(
ne(lancamentos.note, INITIAL_BALANCE_NOTE),
isNull(contas.excludeInitialBalanceFromIncome),
eq(contas.excludeInitialBalanceFromIncome, false),
),
),
)
.groupBy(lancamentos.period, lancamentos.transactionType)
.orderBy(asc(lancamentos.period), asc(lancamentos.transactionType));
const periodTotals = new Map<string, PeriodTotals>();
const periodTotals = new Map<string, PeriodTotals>();
for (const row of rows) {
if (!row.period) continue;
const totals = ensurePeriodTotals(periodTotals, row.period);
const total = safeToNumber(row.totalAmount);
if (row.transactionType === RECEITA) {
totals.receitas += total;
} else if (row.transactionType === DESPESA) {
totals.despesas += Math.abs(total);
}
}
for (const row of rows) {
if (!row.period) continue;
const totals = ensurePeriodTotals(periodTotals, row.period);
const total = safeToNumber(row.totalAmount);
if (row.transactionType === RECEITA) {
totals.receitas += total;
} else if (row.transactionType === DESPESA) {
totals.despesas += Math.abs(total);
}
}
ensurePeriodTotals(periodTotals, period);
ensurePeriodTotals(periodTotals, previousPeriod);
ensurePeriodTotals(periodTotals, period);
ensurePeriodTotals(periodTotals, previousPeriod);
const earliestPeriod =
periodTotals.size > 0 ? Array.from(periodTotals.keys()).sort()[0] : period;
const earliestPeriod =
periodTotals.size > 0 ? Array.from(periodTotals.keys()).sort()[0] : period;
const startPeriod =
comparePeriods(earliestPeriod, previousPeriod) <= 0
? earliestPeriod
: previousPeriod;
const startPeriod =
comparePeriods(earliestPeriod, previousPeriod) <= 0
? earliestPeriod
: previousPeriod;
const periodRange = buildPeriodRange(startPeriod, period);
const forecastByPeriod = new Map<string, number>();
let runningForecast = 0;
const periodRange = buildPeriodRange(startPeriod, period);
const forecastByPeriod = new Map<string, number>();
let runningForecast = 0;
for (const key of periodRange) {
const totals = ensurePeriodTotals(periodTotals, key);
totals.balanco = totals.receitas - totals.despesas;
runningForecast += totals.balanco;
forecastByPeriod.set(key, runningForecast);
}
for (const key of periodRange) {
const totals = ensurePeriodTotals(periodTotals, key);
totals.balanco = totals.receitas - totals.despesas;
runningForecast += totals.balanco;
forecastByPeriod.set(key, runningForecast);
}
const currentTotals = ensurePeriodTotals(periodTotals, period);
const previousTotals = ensurePeriodTotals(periodTotals, previousPeriod);
const currentTotals = ensurePeriodTotals(periodTotals, period);
const previousTotals = ensurePeriodTotals(periodTotals, previousPeriod);
return {
period,
previousPeriod,
receitas: {
current: currentTotals.receitas,
previous: previousTotals.receitas,
},
despesas: {
current: currentTotals.despesas,
previous: previousTotals.despesas,
},
balanco: {
current: currentTotals.balanco,
previous: previousTotals.balanco,
},
previsto: {
current: forecastByPeriod.get(period) ?? runningForecast,
previous: forecastByPeriod.get(previousPeriod) ?? 0,
},
};
return {
period,
previousPeriod,
receitas: {
current: currentTotals.receitas,
previous: previousTotals.receitas,
},
despesas: {
current: currentTotals.despesas,
previous: previousTotals.despesas,
},
balanco: {
current: currentTotals.balanco,
previous: previousTotals.balanco,
},
previsto: {
current: forecastByPeriod.get(period) ?? runningForecast,
previous: forecastByPeriod.get(previousPeriod) ?? 0,
},
};
}