feat: added remaining after budget
parent
8344f20aa7
commit
6c79788957
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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})`;
|
||||
}
|
||||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue