import FinancialCalendar, { getDaysInBaseMonth, } 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 { page } from "fresh"; import { getDate, getMonth, getYear, MakeNewDate, } from "../../../../helpers.ts"; import { createPublicId } from "../../../../helpers.ts"; import { SessionCookie } from "../../../auth/callback.ts"; // Helper function to generate recurring income entries function generateRecurringIncomes( incomes: Income[], targetYear: number, targetMonth: number, ): Income[] { const result: Income[] = []; const baseMonth = targetMonth - 1; for (const income of incomes) { if (!income.frequency || !income.received_date) continue; let currentDate = MakeNewDate(income.received_date); let daysInMonth = getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ); while ( (getYear(currentDate) === targetYear && getMonth(currentDate) <= baseMonth) || getYear(currentDate) < targetYear ) { if (getDate(currentDate) <= daysInMonth) { result.push({ ...income, received_date: currentDate.getTime(), }); } switch (income.frequency.toLowerCase()) { case "daily": currentDate = MakeNewDate( currentDate.getTime() + 24 * 60 * 60 * 1000, ); break; case "biweekly": currentDate = MakeNewDate( currentDate.getTime() + 14 * 24 * 60 * 60 * 1000, ); break; case "monthly": currentDate = MakeNewDate( currentDate.getTime() + getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ) * 24 * 60 * 60 * 1000, ); break; case "one-time": currentDate = MakeNewDate((targetYear + 2) + "-01-01"); break; case "weekly": currentDate = MakeNewDate( currentDate.getTime() + 7 * 24 * 60 * 60 * 1000, ); break; case "quarterly": currentDate = MakeNewDate( currentDate.getTime() + Number( getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ), ) * 3 * 24 * 60 * 60 * 1000, ); break; case "yearly": case "annually": { const monthNumber = getMonth(currentDate) + 1; const monthStr = monthNumber > 9 ? monthNumber : "0" + monthNumber; currentDate = MakeNewDate( (getYear(currentDate) + 1) + "-" + monthStr + "-" + getDate(currentDate), ); break; } default: // Unknown frequency, skip to prevent infinite loop currentDate = MakeNewDate((targetYear + 2) + "-01-01"); break; } daysInMonth = getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ); } } return result; } // Helper function to generate recurring obligation entries function generateRecurringObligations( obligations: Obligation[], targetYear: number, targetMonth: number, ): Obligation[] { const result: Obligation[] = []; const baseMonth = targetMonth - 1; for (const obligation of obligations) { if (!obligation.frequency || !obligation.due_date) continue; let currentDate = MakeNewDate(obligation.due_date); let daysInMonth = getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ); while ( (getYear(currentDate) === targetYear && getMonth(currentDate) <= baseMonth) || getYear(currentDate) < targetYear ) { if (getDate(currentDate) <= daysInMonth) { result.push({ ...obligation, due_date: currentDate.getTime(), }); } switch (obligation.frequency.toLowerCase()) { case "daily": currentDate = MakeNewDate( currentDate.getTime() + 24 * 60 * 60 * 1000, ); break; case "biweekly": currentDate = MakeNewDate( currentDate.getTime() + 14 * 24 * 60 * 60 * 1000, ); break; case "monthly": currentDate = MakeNewDate( currentDate.getTime() + getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ) * 24 * 60 * 60 * 1000, ); break; case "one-time": currentDate = MakeNewDate((targetYear + 2) + "-01-01"); break; case "weekly": currentDate = MakeNewDate( currentDate.getTime() + 7 * 24 * 60 * 60 * 1000, ); break; case "quarterly": currentDate = MakeNewDate( currentDate.getTime() + Number( getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ), ) * 3 * 24 * 60 * 60 * 1000, ); break; case "yearly": case "annually": { const monthNumber = getMonth(currentDate) + 1; const monthStr = monthNumber > 9 ? monthNumber : "0" + monthNumber; currentDate = MakeNewDate( (getYear(currentDate) + 1) + "-" + monthStr + "-" + getDate(currentDate), ); break; } default: // Unknown frequency, skip to prevent infinite loop currentDate = MakeNewDate((targetYear + 2) + "-01-01"); break; } daysInMonth = getDaysInBaseMonth( getYear(currentDate), getMonth(currentDate), ); } } return result; } 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; let date: number | 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 ); obligations = generateRecurringObligations( filteredObligations, yearNum, monthNum, ); incomes = generateRecurringIncomes(filteredIncomes, yearNum, monthNum); const financialBalances = await getFinancialBalancesByUser( stateData.user_id, ); if (financialBalances) { const latestBalance = financialBalances; balance = latestBalance.balance; date = latestBalance.balance_date; } } 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, }); }, 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[] = []; 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 ); obligations = generateRecurringObligations( filteredObligations, yearNum, monthNum, ); incomes = generateRecurringIncomes(filteredIncomes, yearNum, monthNum); console.log("obligations", obligations.length); console.log("incomes", incomes.length); 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 { const _updatedBalance = await upsertFinancialBalance( stateData.user_id, balance, date, ); } } 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, }); }, }); 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; }; }, ) => { return (
); }, );