import "server-only"; import { budgetFor, settingsOf } from "@/features/budgets/service"; import { incomeByDay, incomePlan, monthDailyPlan } from "@/features/budgets/plan"; import { loadContext } from "@/features/transactions/service"; import { budgetPeriod, periodOfDate } from "@/shared/budget"; import { todayIstanbul } from "@/shared/dates"; import { overviewRange, type OverviewPeriod, type OverviewPoint, type OverviewRange, } from "@/shared/overview"; /** * Dashboard ana grafiği (Bölüm 6.13). Bütçe ve gelir planı motorlarıyla aynı kurallar: bütçe ayı, * taksit modu ve gizli hesap ayarı. * * Aylık noktalar: beklenen gelir (gerçekleşen + planlı + elle), bütçe toplamı (bütçe yoksa boş), * gerçekleşen gider. Günlük noktalar: o günün gelirleri (elle eklenen beklenen gelir dönemin ilk * gününe), günlük plan payı, o günün gerçekleşen gideri; gelecek günlerde gerçekleşen boş. */ export type OverviewData = { period: OverviewPeriod; range: OverviewRange; points: OverviewPoint[]; today: string; }; export async function getOverview( userId: string, householdId: string, period: OverviewPeriod, ): Promise { const ctx = await loadContext(userId, householdId, "viewer"); if (!ctx) return null; const s = await settingsOf(ctx.m.householdId); const today = todayIstanbul(); const range = overviewRange(period, today, s.startDay); if (range.granularity === "month") { const points = await Promise.all( range.buckets.map(async (month) => { const [budget, income] = await Promise.all([ budgetFor(ctx, s, month), incomePlan(ctx, s, month), ]); return { key: month, plannedIncome: income.expectedMinor, budget: budget.hasBudget ? budget.totals.limitMinor : null, actual: budget.totals.actualMinor, }; }), ); return { period, range, points, today }; } // Günlük: aralığın dokunduğu bütçe ayları (hafta iki aya yayılabilir). const months = [...new Set([range.from, range.to].map((d) => periodOfDate(d, s.startDay)))]; const planned = new Map(); const actual = new Map(); const income = new Map(); for (const month of months) { const [budget, plan] = await Promise.all([budgetFor(ctx, s, month), incomePlan(ctx, s, month)]); const dp = await monthDailyPlan(ctx, s, month, budget.totals.limitMinor, today); for (const d of dp.days) { planned.set(d.date, budget.hasBudget ? d.plannedMinor : null); actual.set(d.date, d.actualMinor); } const first = budgetPeriod(month, s.startDay).from; if (plan.manualMinor) income.set(first, (income.get(first) ?? 0) + plan.manualMinor); } for (const [d, v] of await incomeByDay(ctx, s, range.from, range.to)) income.set(d, (income.get(d) ?? 0) + v); const points = range.buckets.map((d) => ({ key: d, plannedIncome: income.get(d) ?? 0, budget: planned.get(d) ?? null, actual: d <= today ? (actual.get(d) ?? 0) : null, })); return { period, range, points, today }; }