Last active
June 16, 2024 22:24
-
-
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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