1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import * as actualApi from "@actual-app/api"
import { existsSync, mkdirSync } from "node:fs"
import { join } from "node:path"
import { Temporal } from "temporal-polyfill"
import { CONFIG_DIR } from "./config"
import type { Config } from "./config"
import type { Sb1Transaction, ActualAccount } from "./types"
import type { ImportTransactionEntity } from "@actual-app/api/@types/loot-core/src/types/models/import-transaction"
let inited = false
export async function initActual(config: Config["actual"]) {
if (inited) return
const dataDir = join(CONFIG_DIR, "actualDataDir")
if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true })
process.env.ACTUAL_DATA_DIR = dataDir
await actualApi.init({ password: config.password, serverURL: config.host, dataDir })
await actualApi.downloadBudget(config.fileId)
await actualApi.sync()
inited = true
}
export async function getAccounts(config: Config["actual"]): Promise<ActualAccount[]> {
await initActual(config)
return actualApi.getAccounts() as Promise<ActualAccount[]>
}
export async function importTransactions(
config: Config["actual"],
accountId: string,
transactions: Sb1Transaction[],
dryRun: boolean
) {
await initActual(config)
const mapped: ImportTransactionEntity[] = transactions
.filter(t => t.bookingStatus === "BOOKED")
.map(t => ({
account: accountId,
date: Temporal.Instant.fromEpochMilliseconds(t.date)
.toString({ timeZone: "Europe/Oslo" })
.split("T")[0],
amount: Math.round(t.amount * 100),
payee_name: t.cleanedDescription,
notes: t.description?.toLowerCase().trim() !== t.cleanedDescription?.toLowerCase().trim()
? t.description
: undefined
}))
return actualApi.importTransactions(accountId, mapped, { dryRun })
}
|