feat: added remaining after budget

main
oxypomme 2025-01-02 14:33:24 +01:00
parent 8344f20aa7
commit 6c79788957
6 changed files with 77 additions and 43 deletions

View File

@ -1,9 +1,10 @@
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';
import chalk from 'chalk';
checkEnv([
'BUDGET_INCOME_NAME',
@ -21,24 +22,7 @@ if (process.env.DRY_RUN) {
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, c) {
const operator = s._amountOp === 'isapprox' ? '~ ' : '';
let meta = formatAmount(s._amount);
if (s._date.frequency) {
meta += ` - ${s._date.frequency}`;
}
if (c?.name) {
meta += ` - ${c.name}`;
}
return `${chalk.underline(s.name)} (${operator}${meta})`;
}
const schedules = await actual.getSchedules();
const schedules = await actual.getSchedulesSQL();
const categoriesTotal = new Map();
let totalOutcome = 0;
let totalIncome = 0;
@ -109,4 +93,4 @@ for (const [id, total] of categoriesTotal) {
console.log();
await actual.shutdown();
await actual.shutdown();

29
get-remaining.js Normal file
View File

@ -0,0 +1,29 @@
import { format } from 'date-fns';
import chalk from 'chalk';
import * as actual from './lib/actual.js';
import { formatAmount, formatSchedule } from './lib/format.js';
import { checkEnv } from './lib/env.js';
checkEnv([
'ACCOUNT_ID',
]);
const month = format(new Date(), 'yyyy-MM');
const balance = await actual.getAccountBalance(process.env.ACCOUNT_ID);
const budget = await actual.getBudgetMonth(month);
const remaining = balance - budget.totalBalance;
console.log();
console.log(`Balance: ${formatAmount(balance)}`);
// TODO: what if no income yet
console.log(`Budget balance for ${month}: ${chalk.red(formatAmount(budget.totalBalance))}`);
console.log(`Remaining: ${chalk.blue(formatAmount(remaining))}`);
console.log();
console.log(`Based on income for ${month}: you've got ${chalk.bgBlue((remaining / budget.totalBudgeted).toFixed(2))} times your budget left, even after spending everything`);
console.log();
await actual.shutdown();

View File

@ -19,9 +19,8 @@ await api.downloadBudget(process.env.ACTUAL_ID, {
password: process.env.ACTUAL_ENCRYPTION,
});
export async function getSchedules(select = '*') {
export async function getSchedulesSQL(select = '*') {
return (await api.runQuery(api.q('schedules').select(select))).data;
}
export * from '@actual-app/api';
export * from '@actual-app/api';

18
lib/format.js Normal file
View File

@ -0,0 +1,18 @@
import chalk from 'chalk';
const amountFormatter = new Intl.NumberFormat(undefined, { style: 'currency', currency: 'EUR' });
export function formatAmount(amount) {
return chalk.bold(amountFormatter.format(Math.abs(amount / 100)));
}
export function formatSchedule(s, c) {
const operator = s._amountOp === 'isapprox' ? '~ ' : '';
let meta = formatAmount(s._amount);
if (s._date.frequency) {
meta += ` - ${s._date.frequency}`;
}
if (c?.name) {
meta += ` - ${c.name}`;
}
return `${chalk.underline(s.name)} (${operator}${meta})`;
}

36
package-lock.json generated
View File

@ -9,16 +9,17 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@actual-app/api": "^6.8.2",
"chalk": "^5.3.0",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5"
"@actual-app/api": "^24.12.0",
"chalk": "^5.4.1",
"date-fns": "^4.1.0",
"dotenv": "^16.4.7"
}
},
"node_modules/@actual-app/api": {
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@actual-app/api/-/api-6.8.2.tgz",
"integrity": "sha512-BI9Zqip6eBRwmgQjDgXHpPSIJYsbm6e+Yyyvj9sNRn9GbWZr7WXsA8SySlIOZH/8EsA6DpS7CIW0RfOMWuLEcg==",
"version": "24.12.0",
"resolved": "https://registry.npmjs.org/@actual-app/api/-/api-24.12.0.tgz",
"integrity": "sha512-LT7mnp79xpBZAfrRMdKpiZKQEQ+zDY3nopwFaZQTUoY05BwpoypujC5TQe6q3QKBm3aFrq0bZXBa/hSnVAOvgw==",
"license": "MIT",
"dependencies": {
"@actual-app/crdt": "^2.1.0",
"better-sqlite3": "^9.6.0",
@ -111,9 +112,10 @@
}
},
"node_modules/chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
@ -140,9 +142,10 @@
}
},
"node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
@ -179,9 +182,10 @@
}
},
"node_modules/dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},

View File

@ -11,9 +11,9 @@
"author": "oxypomme",
"license": "MIT",
"dependencies": {
"@actual-app/api": "^6.8.2",
"chalk": "^5.3.0",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5"
"@actual-app/api": "^24.12.0",
"chalk": "^5.4.1",
"date-fns": "^4.1.0",
"dotenv": "^16.4.7"
}
}