diff --git a/calc-schedule-budget.js b/calc-schedule-budget.js index 34e1b41..94cb8d6 100644 --- a/calc-schedule-budget.js +++ b/calc-schedule-budget.js @@ -3,30 +3,56 @@ import { format } from 'date-fns'; import { checkEnv } from './lib/env.js'; import * as actual from './lib/actual.js'; import { addFromFrequency } from './lib/dates.js'; +import chalk from 'chalk'; checkEnv([ 'BUDGET_INCOME_NAME', '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 amountFormatter = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' }); +function formatAmount(amount) { + return chalk.bold(amountFormatter.format(Math.abs(amount / 100))); +} + +function formatSchedule(s) { + const operator = s._amountOp === 'isapprox' ? '~ ' : ''; + let meta = formatAmount(s._amount); + if (s._date.frequency) { + meta += ` - ${s._date.frequency}`; + } + return `${chalk.underline(s.name)} (${operator}${meta})`; +} + const schedules = await actual.getSchedules(); let totalOutcome = 0; let totalIncome = 0; for (const s of schedules) { - // TODO: get end - if (s._date.start.slice(0, 7) > month) { - console.log(`Skipping ${s.name} cause not in range`); + if (s.completed) { + console.log(chalk.grey(`- ${formatSchedule(s)}: completed`)); continue; } - - let date = s._date.start.slice(0, 7); + + const start = (typeof s._date === 'string' ? s._date : s._date.start).slice(0, 7); + // TODO: get end + if (start > month) { + console.log(chalk.grey(`- ${formatSchedule(s)}: not in range`)); + continue; + } + + let date = start; while (date < month) { - date = addFromFrequency(date, s._date.frequency); + date = addFromFrequency(date, s._date.frequency, s._date.interval); } if (date !== month) { - console.log(`Skipping ${s.name} cause not this month`); + console.log(chalk.grey(`- ${formatSchedule(s)}: not the specified month`)); continue; } @@ -35,20 +61,26 @@ for (const s of schedules) { } else { totalOutcome += Math.abs(s._amount); } + console.log(chalk.green(`+ ${formatSchedule(s)}`)); } -console.log(`\nTotal income: +${totalIncome / 100}`); -console.log(`Total outcome: -${totalOutcome / 100}\n`); +console.log(chalk.blue(`\nTotal income: +${formatAmount(totalIncome)}\nTotal outcome: -${formatAmount(totalOutcome)}\n`)); -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); +if (!process.env.DRY_RUN) { + 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); -if (incomeCategory) { - actual.setBudgetAmount(month, incomeCategory.id, totalIncome); -} -if (outcomeCategory) { - actual.setBudgetAmount(month, outcomeCategory.id, totalOutcome); + if (incomeCategory) { + actual.setBudgetAmount(month, incomeCategory.id, totalIncome); + console.log(chalk.yellow(`Income set for "${process.env.BUDGET_INCOME_NAME}" at "${month}"`)); + } + if (outcomeCategory) { + actual.setBudgetAmount(month, outcomeCategory.id, totalOutcome); + console.log(chalk.yellow(`Outcome set for "${process.env.BUDGET_OUTCOME_NAME}" at "${month}"`)) + } } +console.log(); + await actual.shutdown(); \ No newline at end of file diff --git a/lib/actual.js b/lib/actual.js index d41de33..9eb01a2 100644 --- a/lib/actual.js +++ b/lib/actual.js @@ -21,7 +21,7 @@ await api.downloadBudget(process.env.ACTUAL_ID, { export async function getSchedules(select = '*') { - return (await api.runQuery(api.q('schedules').select(select).filter({ completed: false }))).data; + return (await api.runQuery(api.q('schedules').select(select))).data; } export * from '@actual-app/api'; \ No newline at end of file diff --git a/lib/dates.js b/lib/dates.js index 9375123..b6dc553 100644 --- a/lib/dates.js +++ b/lib/dates.js @@ -2,20 +2,20 @@ import { add, parse, format } from 'date-fns'; const startNow = new Date(); -export function addFromFrequency(date, frequency, f = 'yyyy-MM') { +export function addFromFrequency(date, frequency, interval = 1, f = 'yyyy-MM') { const toAdd = {}; switch (frequency) { case 'daily': - toAdd.days = 1; + toAdd.days = interval; break; case 'weekly': - toAdd.weeks = 1; + toAdd.weeks = interval; break; case 'monthly': - toAdd.months = 1; + toAdd.months = interval; break; case 'yearly': - toAdd.years = 1; + toAdd.years = interval; break; default: diff --git a/package-lock.json b/package-lock.json index 000594a..ddbb1b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@actual-app/api": "^6.8.2", + "chalk": "^5.3.0", "date-fns": "^3.6.0", "dotenv": "^16.4.5" } @@ -109,6 +110,17 @@ "ieee754": "^1.1.13" } }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", diff --git a/package.json b/package.json index 0bc1691..a4b919f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "MIT", "dependencies": { "@actual-app/api": "^6.8.2", + "chalk": "^5.3.0", "date-fns": "^3.6.0", "dotenv": "^16.4.5" }