import FinancialCalendar from "../../../../islands/FinancialCalendar.tsx"; import { define } from "../../../../utils.ts"; import { getIncomesByUser, getObligationsByUser, Income, Obligation, } from "../../../../services/financhalServices.ts"; import { getFinancialBalancesByUser, upsertFinancialBalance, } from "../../../../services/userService.ts"; import { computeMonthlyBalances, generateRecurringIncomesForMonth, generateRecurringObligationsForMonth, invalidateMonthCache, type MonthlyBalanceCache, } from "../../../../services/dailyBalanceCache.ts"; import { page } from "fresh"; import { getMonth, getYear } from "../../../../helpers.ts"; import { createPublicId } from "../../../../helpers.ts"; import { SessionCookie } from "../../../auth/callback.ts"; export const handler = define.handlers({ GET: async (ctx) => { const params = ctx.params; let yearNum = parseInt(params.year, 10); let monthNum = parseInt(params.month, 10); if (isNaN(yearNum) || isNaN(monthNum)) { const fallback = `/calendar/financial-calendar/${getYear(new Date())}/${ getMonth(new Date()) + 1 }`; return new Response(null, { status: 302, headers: { Location: fallback }, }); } if (yearNum < 1900 || yearNum > 2100 || monthNum < 1 || monthNum > 12) { yearNum = getYear(new Date()); monthNum = getMonth(new Date()) + 1; } const state = ctx.state; const stateData: SessionCookie = state.data; const publicId = stateData?.user_id ? await createPublicId(stateData.user_id) : ""; const isEncrypted = stateData?.user_encrypted; let obligations: Obligation[] = []; let incomes: Income[] = []; let balance: number | null = null; let date: number | null = null; let monthlyCache: MonthlyBalanceCache | null = null; if (stateData?.user_id) { const rawObligations = await getObligationsByUser(stateData.user_id) ?? []; const filteredObligations = rawObligations.filter((ob) => ob.id && ob.user_id && ob.frequency && ob.due_date && ob.created_on && ob.updated_on ); const rawIncomes = await getIncomesByUser(stateData.user_id) ?? []; const filteredIncomes = rawIncomes.filter((inc) => inc.id && inc.user_id && inc.frequency && inc.received_date && inc.created_on && inc.updated_on ); // Get base balance const financialBalances = await getFinancialBalancesByUser( stateData.user_id, ); let baseBalance: { date: string; balance: number } | null = null; if (financialBalances) { balance = financialBalances.balance ?? null; date = financialBalances.balance_date ?? null; if (balance !== null && date !== null) { baseBalance = { date: new Date(date).toISOString().slice(0, 10), balance: balance, }; } } // Compute or retrieve cached monthly balances monthlyCache = await computeMonthlyBalances( stateData.user_id, yearNum, monthNum, filteredIncomes, filteredObligations, baseBalance, ); // Generate recurring entries for the current month for display // (used for day markers and list views - retains encrypted_data) obligations = generateRecurringObligationsForMonth( filteredObligations, yearNum, monthNum, ); incomes = generateRecurringIncomesForMonth( filteredIncomes, yearNum, monthNum, ); } return page({ stateData, obligations, incomes, params, yearNum, monthNum, balance: balance!, date: date ? new Date(date).toISOString().slice(0, 10) : null, publicId, isEncrypted, urlRoute: `/calendar/financial-calendar/${yearNum}/${monthNum}`, stateId: state.stateId, monthlyCache, }); }, POST: async (ctx) => { const params = ctx.params; const formData = await ctx.req.formData(); let balance: number | null = formData.get("balance") ? Number(formData.get("balance")) : null; let date: number | null = formData.get("date") ? Date.parse(formData.get("date") as string) : null; let yearNum = parseInt(params.year, 10); let monthNum = parseInt(params.month, 10); if (isNaN(yearNum) || isNaN(monthNum)) { const fallback = `/calendar/financial-calendar/${getYear(new Date())}/${ getMonth(new Date()) + 1 }`; return new Response(null, { status: 302, headers: { Location: fallback }, }); } if (yearNum < 1900 || yearNum > 2100 || monthNum < 1 || monthNum > 12) { yearNum = getYear(new Date()); monthNum = getMonth(new Date()) + 1; } console.log("POST url", ctx.req.url); const state = ctx.state; const stateData: SessionCookie = state.data; const publicId = stateData?.user_id ? await createPublicId(stateData.user_id) : ""; const isEncrypted = stateData?.user_encrypted; let obligations: Obligation[] = []; let incomes: Income[] = []; let monthlyCache: MonthlyBalanceCache | null = null; if (stateData?.user_id) { const rawObligations = await getObligationsByUser(stateData.user_id) ?? []; const filteredObligations = rawObligations.filter((ob) => ob.id && ob.user_id && ob.frequency && ob.due_date && ob.created_on && ob.updated_on ); const rawIncomes = await getIncomesByUser(stateData.user_id) ?? []; const filteredIncomes = rawIncomes.filter((inc) => inc.id && inc.user_id && inc.frequency && inc.received_date && inc.created_on && inc.updated_on ); if (!balance || !date) { const financialBalances = await getFinancialBalancesByUser( stateData.user_id, ); if (financialBalances) { const latestBalance = financialBalances; if (!balance) { balance = latestBalance.balance ? latestBalance.balance : null; } if (!date) { date = latestBalance.balance_date ? latestBalance.balance_date : null; } } } else { // Update balance and invalidate cache for this month await upsertFinancialBalance( stateData.user_id, balance, date, ); // Invalidate cache so next request recomputes with new base balance await invalidateMonthCache(stateData.user_id, yearNum, monthNum); } // Compute or retrieve cached monthly balances let baseBalance: { date: string; balance: number } | null = null; if (balance !== null && date !== null) { baseBalance = { date: new Date(date).toISOString().slice(0, 10), balance: balance, }; } monthlyCache = await computeMonthlyBalances( stateData.user_id, yearNum, monthNum, filteredIncomes, filteredObligations, baseBalance, ); // Generate recurring entries for the current month for display // (used for day markers and list views - retains encrypted_data) obligations = generateRecurringObligationsForMonth( filteredObligations, yearNum, monthNum, ); incomes = generateRecurringIncomesForMonth( filteredIncomes, yearNum, monthNum, ); } return page({ stateData, obligations, incomes, params, yearNum, monthNum, balance, date: date ? new Date(date).toISOString().slice(0, 10) : null, publicId, isEncrypted, urlRoute: `/calendar/financial-calendar/${yearNum}/${monthNum}`, stateId: state.stateId, monthlyCache, }); }, }); export default define.page( ( { data }: { data: { stateData: SessionCookie; obligations: Obligation[]; incomes: Income[]; yearNum: number; monthNum: number; balance?: string; date?: string; publicId: string; isEncrypted: boolean; urlRoute: string; stateId: string; monthlyCache?: MonthlyBalanceCache | null; }; }, ) => { return (
); }, );