import { getRecentEstablishmentsAction } from "@/app/(dashboard)/lancamentos/actions"; import { cartoes, lancamentos } from "@/db/schema"; import { db } from "@/lib/db"; import { buildOptionSets, buildSluggedFilters, fetchLancamentoFilterSources, mapLancamentosData, } from "@/lib/lancamentos/page-helpers"; import { fetchUserPeriodPreferences } from "@/lib/user-preferences/period"; import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants"; import { and, eq, gte, lte, ne, or } from "drizzle-orm"; import type { CalendarData, CalendarEvent, } from "@/components/calendario/types"; const PAYMENT_METHOD_BOLETO = "Boleto"; const TRANSACTION_TYPE_TRANSFERENCIA = "Transferência"; const toDateKey = (date: Date) => date.toISOString().slice(0, 10); const parsePeriod = (period: string) => { const [yearStr, monthStr] = period.split("-"); const year = Number.parseInt(yearStr ?? "", 10); const month = Number.parseInt(monthStr ?? "", 10); if (Number.isNaN(year) || Number.isNaN(month) || month < 1 || month > 12) { throw new Error(`Período inválido: ${period}`); } return { year, monthIndex: month - 1 }; }; const clampDayInMonth = (year: number, monthIndex: number, day: number) => { const lastDay = new Date(Date.UTC(year, monthIndex + 1, 0)).getUTCDate(); if (day < 1) return 1; if (day > lastDay) return lastDay; return day; }; const isWithinRange = (value: string | null, start: string, end: string) => { if (!value) return false; return value >= start && value <= end; }; type FetchCalendarDataParams = { userId: string; period: string; }; export const fetchCalendarData = async ({ userId, period, }: FetchCalendarDataParams): Promise => { const { year, monthIndex } = parsePeriod(period); const rangeStart = new Date(Date.UTC(year, monthIndex, 1)); const rangeEnd = new Date(Date.UTC(year, monthIndex + 1, 0)); const rangeStartKey = toDateKey(rangeStart); const rangeEndKey = toDateKey(rangeEnd); const [lancamentoRows, cardRows, filterSources, periodPreferences] = await Promise.all([ db.query.lancamentos.findMany({ where: and( eq(lancamentos.userId, userId), ne(lancamentos.transactionType, TRANSACTION_TYPE_TRANSFERENCIA), or( // Lançamentos cuja data de compra esteja no período do calendário and( gte(lancamentos.purchaseDate, rangeStart), lte(lancamentos.purchaseDate, rangeEnd) ), // Boletos cuja data de vencimento esteja no período do calendário and( eq(lancamentos.paymentMethod, PAYMENT_METHOD_BOLETO), gte(lancamentos.dueDate, rangeStart), lte(lancamentos.dueDate, rangeEnd) ), // Lançamentos de cartão do período (para calcular totais de vencimento) and( eq(lancamentos.period, period), ne(lancamentos.paymentMethod, PAYMENT_METHOD_BOLETO) ) ) ), with: { pagador: true, conta: true, cartao: true, categoria: true, }, }), db.query.cartoes.findMany({ where: eq(cartoes.userId, userId), }), fetchLancamentoFilterSources(userId), fetchUserPeriodPreferences(userId), ]); const lancamentosData = mapLancamentosData(lancamentoRows); const events: CalendarEvent[] = []; const cardTotals = new Map(); for (const item of lancamentosData) { if ( !item.cartaoId || item.period !== period || item.pagadorRole !== PAGADOR_ROLE_ADMIN ) { continue; } const amount = Math.abs(item.amount ?? 0); cardTotals.set( item.cartaoId, (cardTotals.get(item.cartaoId) ?? 0) + amount ); } for (const item of lancamentosData) { const isBoleto = item.paymentMethod === PAYMENT_METHOD_BOLETO; const isAdminPagador = item.pagadorRole === PAGADOR_ROLE_ADMIN; // Para boletos, exibir apenas na data de vencimento e apenas se for pagador admin if (isBoleto) { if ( isAdminPagador && item.dueDate && isWithinRange(item.dueDate, rangeStartKey, rangeEndKey) ) { events.push({ id: `${item.id}:boleto`, type: "boleto", date: item.dueDate, lancamento: item, }); } } else { // Para outros tipos de lançamento, exibir na data de compra if (!isAdminPagador) { continue; } const purchaseDateKey = item.purchaseDate.slice(0, 10); if (isWithinRange(purchaseDateKey, rangeStartKey, rangeEndKey)) { events.push({ id: item.id, type: "lancamento", date: purchaseDateKey, lancamento: item, }); } } } // Exibir vencimentos apenas de cartões com lançamentos do pagador admin for (const card of cardRows) { if (!cardTotals.has(card.id)) { continue; } const dueDayNumber = Number.parseInt(card.dueDay ?? "", 10); if (Number.isNaN(dueDayNumber)) { continue; } const normalizedDay = clampDayInMonth(year, monthIndex, dueDayNumber); const dueDateKey = toDateKey( new Date(Date.UTC(year, monthIndex, normalizedDay)) ); events.push({ id: `${card.id}:cartao`, type: "cartao", date: dueDateKey, card: { id: card.id, name: card.name, dueDay: card.dueDay, closingDay: card.closingDay, brand: card.brand ?? null, status: card.status, logo: card.logo ?? null, totalDue: cardTotals.get(card.id) ?? null, }, }); } const typePriority: Record = { lancamento: 0, boleto: 1, cartao: 2, }; events.sort((a, b) => { if (a.date === b.date) { return typePriority[a.type] - typePriority[b.type]; } return a.date.localeCompare(b.date); }); const sluggedFilters = buildSluggedFilters(filterSources); const optionSets = buildOptionSets({ ...sluggedFilters, pagadorRows: filterSources.pagadorRows, }); const estabelecimentos = await getRecentEstablishmentsAction(); return { events, formOptions: { pagadorOptions: optionSets.pagadorOptions, splitPagadorOptions: optionSets.splitPagadorOptions, defaultPagadorId: optionSets.defaultPagadorId, contaOptions: optionSets.contaOptions, cartaoOptions: optionSets.cartaoOptions, categoriaOptions: optionSets.categoriaOptions, estabelecimentos, periodPreferences, }, }; };