import { format } from 'date-fns'; import chalk from 'chalk'; import { checkEnv } from './lib/env.js'; import { formatAmount, formatSchedule } from './lib/format.js'; import * as actual from './lib/actual.js'; import { addFromFrequency } from './lib/dates.js'; checkEnv([ 'BUDGET_INCOME_NAME', 'BUDGET_OUTCOME_NAME', ]); const categories = await actual.getCategories(); const incomeCategory = categories.find(c => c.name === process.env.BUDGET_INCOME_NAME); const outcomeCategory = categories.find(c => c.name === process.env.BUDGET_OUTCOME_NAME); console.log(); if (process.env.DRY_RUN) { console.log(chalk.grey.bold('Running in dry run mode\n')); } const [month = format(new Date(), 'yyyy-MM')] = process.argv.slice(2); const schedules = await actual.getSchedulesSQL(); const categoriesTotal = new Map(); let totalOutcome = 0; let totalIncome = 0; for (const s of schedules) { if (s.completed) { console.log(chalk.grey(`- ${formatSchedule(s)}: completed`)); continue; } const start = (typeof s._date === 'string' ? s._date : s._date.start).slice(0, 7); if (start > month) { console.log(chalk.grey(`- ${formatSchedule(s)}: not in range`)); continue; } let date = start; if (typeof s._date === 'object') { let occurrences = 0; while (date < month && occurrences <= s._date.endOccurrences) { if (s._date.endMode !== 'never') { occurrences += 1; } date = addFromFrequency(date, s._date.frequency, s._date.interval); } } if (date !== month) { console.log(chalk.grey(`- ${formatSchedule(s)}: not the specified month`)); continue; } const amount = Math.abs(s._amount); let categoryId = s._actions.find((a) => a.field === 'category' && a.op === 'set')?.value; if (!categoryId) { console.log(chalk.yellow(` ${formatSchedule(s)}: falling back on default`)); categoryId = (s._amount > 0 ? incomeCategory?.id : outcomeCategory?.id); } if (!categoryId) { console.log(chalk.red(`- ${formatSchedule(s)}: no category found`)); continue; } if (s._amount > 0) { totalIncome += amount; } else { totalOutcome += amount; } categoriesTotal.set(categoryId, (categoriesTotal.get(categoryId) || 0) + amount); console.log(chalk.green(`+ ${formatSchedule(s, categories.find((c) => c.id === categoryId))}`)); } console.log(chalk.bgBlue(`\nTotal income: +${formatAmount(totalIncome)}\nTotal outcome: -${formatAmount(totalOutcome)}\n`)); console.log(`Budgets for ${month}: `); for (const [id, total] of categoriesTotal) { const category = categories.find((c) => c.id === id); if (!category) { console.log(chalk.red(`Category ${id} not found`)); continue; } if (!process.env.DRY_RUN) { actual.setBudgetAmount(month, id, total); } console.log(chalk.blue(`${chalk.underline(category.name)} -> ${formatAmount(total)}`)); } console.log(); await actual.shutdown();