Skip to content

Instantly share code, notes, and snippets.

@psybers
Last active May 8, 2024 15:40
Show Gist options
  • Save psybers/cc4ac3d5c2468d416196a0fa1e2975d4 to your computer and use it in GitHub Desktop.
Save psybers/cc4ac3d5c2468d416196a0fa1e2975d4 to your computer and use it in GitHub Desktop.
Automatically insert Actual Budget transactions for loan interest. Add "interestRate:0.xx interestDay:yy" to the account note, where 'yy' is the day of month to insert the transaction. Run `npm install` to install packages, then `node apply-interest.js` to run it (I run it in a daily cron).
require("dotenv").config();
const api = require('@actual-app/api');
const payeeName = process.env.IMPORTER_PAYEE_NAME || 'Loan Interest';
const url = process.env.ACTUAL_SERVER_URL || '';
const password = process.env.ACTUAL_SERVER_PASSWORD || '';
const sync_id = process.env.ACTUAL_SYNC_ID || '';
const cache = process.env.IMPORTER_CACHE_DIR || './cache';
if (!url || !password || !sync_id) {
console.error('Required settings for Actual not provided.');
process.exit(1);
}
const getAccountBalance = async (account, cutoffDate=new Date()) => {
const data = await api.runQuery(
api.q('transactions')
.filter({
'account': account.id,
'date': { $lt: cutoffDate },
})
.calculate({ $sum: '$amount' })
.options({ splits: 'grouped' })
);
return data.data;
};
const getLastTransactionDate = async (account, cutoffDate=new Date()) => {
const data = await api.runQuery(
api.q('transactions')
.filter({
'account': account.id,
'date': { $lt: cutoffDate },
'amount': { $gt: 0 },
})
.select('date')
.orderBy({ 'date': 'desc' })
.limit(1)
.options({ splits: 'grouped' })
);
return data.data[0].date;
};
(async () => {
console.log("connect");
await api.init({ serverURL: url, password: password, dataDir: cache });
console.log("open file");
await api.downloadBudget(sync_id);
const payees = await api.getPayees();
const payee = payees.find(p => p.name === payeeName);
const accounts = await api.getAccounts();
for (const account of accounts) {
if (account.closed) {
continue;
}
const notes = await api.runQuery(
api.q('notes')
.filter({
id: `account-${account.id}`,
})
.select('*')
);
if (notes.data.length && notes.data[0].note) {
const note = notes.data[0].note;
if (note.indexOf('interestRate:') > -1 && note.indexOf('interestDay:') > -1) {
const interestRate = parseFloat(note.split('interestRate:')[1].split(' ')[0]);
const interestDay = parseInt(note.split('interestDay:')[1].split(' ')[0]);
const interestTransactionDate = new Date();
if (interestTransactionDate.getDate() < interestDay) {
interestTransactionDate.setMonth(interestTransactionDate.getMonth() - 1);
}
interestTransactionDate.setDate(interestDay);
interestTransactionDate.setHours(5, 0, 0, 0);
const cutoff = new Date(interestTransactionDate);
cutoff.setMonth(cutoff.getMonth() - 1);
cutoff.setDate(cutoff.getDate() + 1);
const lastDate = await getLastTransactionDate(account, cutoff);
const balance = await getAccountBalance(account, interestTransactionDate);
const daysPassed = Math.floor((interestTransactionDate - new Date(lastDate)) / 86400000);
const compoundedInterest = Math.round(balance * (Math.pow(1 + interestRate / 12, 1) - 1));
console.log(`== ${account.name} ==`);
console.log(` -> Balance: ${balance}`);
console.log(` as of ${lastDate}`);
console.log(` -> # days: ${daysPassed}`);
console.log(` -> Interest: ${compoundedInterest}`)
await api.importTransactions(account.id, [{
date: interestTransactionDate,
payee: payee.id,
amount: compoundedInterest,
notes: `Interest for 1 month at ${interestRate}%`,
}]);
}
}
}
console.log("done");
await api.shutdown();
})();
{
"dependencies": {
"@actual-app/api": "^6.7.0",
"dotenv": "^16.4.5"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment