@fuzzle/opencode-accountant 0.16.0 → 0.16.1-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +78 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23515,7 +23515,8 @@ function balancesMatch(balance1, balance2) {
|
|
|
23515
23515
|
return false;
|
|
23516
23516
|
}
|
|
23517
23517
|
validateCurrencies(parsed1, parsed2);
|
|
23518
|
-
|
|
23518
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
23519
|
+
return round2(parsed1.amount) === round2(parsed2.amount);
|
|
23519
23520
|
}
|
|
23520
23521
|
|
|
23521
23522
|
// src/utils/csvParser.ts
|
|
@@ -24035,7 +24036,7 @@ function buildRetryCommand(contextId, closingBalance, account) {
|
|
|
24035
24036
|
}
|
|
24036
24037
|
return parts.join(" ");
|
|
24037
24038
|
}
|
|
24038
|
-
function determineAccount(csvFile, rulesDir, importContext, manualAccount, relativeCsvPath, metadata) {
|
|
24039
|
+
function determineAccount(csvFile, rulesDir, directory, importContext, manualAccount, relativeCsvPath, metadata) {
|
|
24039
24040
|
let account = manualAccount;
|
|
24040
24041
|
const rulesMapping = loadRulesMapping(rulesDir);
|
|
24041
24042
|
const rulesFile = findRulesForCsv(csvFile, rulesMapping);
|
|
@@ -24043,6 +24044,12 @@ function determineAccount(csvFile, rulesDir, importContext, manualAccount, relat
|
|
|
24043
24044
|
if (rulesFile) {
|
|
24044
24045
|
account = getAccountFromRulesFile(rulesFile) ?? undefined;
|
|
24045
24046
|
}
|
|
24047
|
+
if (!account && importContext.rulesFile) {
|
|
24048
|
+
const contextRulesPath = path10.join(directory, importContext.rulesFile);
|
|
24049
|
+
if (fs11.existsSync(contextRulesPath)) {
|
|
24050
|
+
account = getAccountFromRulesFile(contextRulesPath) ?? undefined;
|
|
24051
|
+
}
|
|
24052
|
+
}
|
|
24046
24053
|
}
|
|
24047
24054
|
if (!account) {
|
|
24048
24055
|
const rulesHint = rulesFile ? `Add 'account1 assets:bank:...' to ${rulesFile} or retry with: ${buildRetryCommand(importContext.id, undefined, "assets:bank:...")}` : `Create a rules file in ${rulesDir} with 'account1' directive or retry with: ${buildRetryCommand(importContext.id, undefined, "assets:bank:...")}`;
|
|
@@ -24160,7 +24167,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
|
|
|
24160
24167
|
return balanceResult.error;
|
|
24161
24168
|
}
|
|
24162
24169
|
const { closingBalance, metadata, fromCSVAnalysis } = balanceResult;
|
|
24163
|
-
const accountResult = determineAccount(csvFile, rulesDir, importContext, options.account, relativeCsvPath, metadata);
|
|
24170
|
+
const accountResult = determineAccount(csvFile, rulesDir, directory, importContext, options.account, relativeCsvPath, metadata);
|
|
24164
24171
|
if ("error" in accountResult) {
|
|
24165
24172
|
return accountResult.error;
|
|
24166
24173
|
}
|
|
@@ -26962,6 +26969,34 @@ function generateIbkrAdjustmentEntry(adjustment, logger) {
|
|
|
26962
26969
|
`;
|
|
26963
26970
|
return entry;
|
|
26964
26971
|
}
|
|
26972
|
+
function generateIbkrForexEntry(entry, logger) {
|
|
26973
|
+
const description = escapeDescription(entry.description);
|
|
26974
|
+
logger?.debug(`Generating IBKR Forex entry: ${entry.amount} ${entry.currency}`);
|
|
26975
|
+
const postings = [
|
|
26976
|
+
{
|
|
26977
|
+
account: `assets:broker:ibkr:${entry.currency.toLowerCase()}`,
|
|
26978
|
+
amount: formatAmount(entry.amount, entry.currency)
|
|
26979
|
+
}
|
|
26980
|
+
];
|
|
26981
|
+
if (entry.amount < 0) {
|
|
26982
|
+
postings.push({
|
|
26983
|
+
account: "expenses:fees:forex:ibkr",
|
|
26984
|
+
amount: formatAmount(Math.abs(entry.amount), entry.currency)
|
|
26985
|
+
});
|
|
26986
|
+
} else {
|
|
26987
|
+
postings.push({
|
|
26988
|
+
account: "income:fx-gains:ibkr",
|
|
26989
|
+
amount: formatAmount(-entry.amount, entry.currency)
|
|
26990
|
+
});
|
|
26991
|
+
}
|
|
26992
|
+
let result = `${entry.date} ${description}
|
|
26993
|
+
`;
|
|
26994
|
+
result += ` ; ibkr:account:${entry.account}
|
|
26995
|
+
`;
|
|
26996
|
+
result += formatPostings(postings) + `
|
|
26997
|
+
`;
|
|
26998
|
+
return result;
|
|
26999
|
+
}
|
|
26965
27000
|
|
|
26966
27001
|
// src/utils/ibkrCsvPreprocessor.ts
|
|
26967
27002
|
var TRADE_TYPES2 = new Set(["Buy", "Sell"]);
|
|
@@ -27058,7 +27093,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
|
|
|
27058
27093
|
trades: 0,
|
|
27059
27094
|
dividends: 0,
|
|
27060
27095
|
adjustments: 0,
|
|
27061
|
-
|
|
27096
|
+
forex: 0
|
|
27062
27097
|
},
|
|
27063
27098
|
alreadyPreprocessed: true
|
|
27064
27099
|
};
|
|
@@ -27070,7 +27105,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
|
|
|
27070
27105
|
const trades = [];
|
|
27071
27106
|
const dividendTxns = [];
|
|
27072
27107
|
const adjustments = [];
|
|
27073
|
-
|
|
27108
|
+
const forexTxns = [];
|
|
27074
27109
|
for (const txn of transactions) {
|
|
27075
27110
|
if (TRADE_TYPES2.has(txn.transactionType)) {
|
|
27076
27111
|
trades.push(txn);
|
|
@@ -27081,7 +27116,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
|
|
|
27081
27116
|
} else if (txn.transactionType === DEPOSIT_TYPE) {
|
|
27082
27117
|
deposits.push(txn);
|
|
27083
27118
|
} else if (txn.transactionType === FOREX_TYPE) {
|
|
27084
|
-
|
|
27119
|
+
forexTxns.push(txn);
|
|
27085
27120
|
} else {
|
|
27086
27121
|
logger?.warn(`Unknown IBKR transaction type: ${txn.transactionType}`);
|
|
27087
27122
|
}
|
|
@@ -27167,10 +27202,41 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
|
|
|
27167
27202
|
usedAccounts.add("income:fx-gains:ibkr");
|
|
27168
27203
|
}
|
|
27169
27204
|
}
|
|
27205
|
+
if (forexTxns.length > 0) {
|
|
27206
|
+
const forexByDate = new Map;
|
|
27207
|
+
for (const txn of forexTxns) {
|
|
27208
|
+
const existing = forexByDate.get(txn.date);
|
|
27209
|
+
if (existing) {
|
|
27210
|
+
existing.total += txn.netAmount;
|
|
27211
|
+
} else {
|
|
27212
|
+
forexByDate.set(txn.date, { account: txn.account, total: txn.netAmount });
|
|
27213
|
+
}
|
|
27214
|
+
}
|
|
27215
|
+
const sortedDates = [...forexByDate.keys()].sort();
|
|
27216
|
+
for (const date5 of sortedDates) {
|
|
27217
|
+
const { account, total } = forexByDate.get(date5);
|
|
27218
|
+
const amount = Math.round(total * 100) / 100;
|
|
27219
|
+
if (amount === 0)
|
|
27220
|
+
continue;
|
|
27221
|
+
const forexEntry = {
|
|
27222
|
+
date: date5,
|
|
27223
|
+
account,
|
|
27224
|
+
description: "Forex conversion cost",
|
|
27225
|
+
amount,
|
|
27226
|
+
currency: currency.toUpperCase()
|
|
27227
|
+
};
|
|
27228
|
+
journalEntries.push(generateIbkrForexEntry(forexEntry, logger));
|
|
27229
|
+
usedAccounts.add(`assets:broker:ibkr:${currency}`);
|
|
27230
|
+
if (amount < 0) {
|
|
27231
|
+
usedAccounts.add("expenses:fees:forex:ibkr");
|
|
27232
|
+
} else {
|
|
27233
|
+
usedAccounts.add("income:fx-gains:ibkr");
|
|
27234
|
+
}
|
|
27235
|
+
}
|
|
27236
|
+
}
|
|
27170
27237
|
saveLotInventory(directory, lotInventoryPath, inventory, logger);
|
|
27171
27238
|
let journalFilePath = null;
|
|
27172
27239
|
if (journalEntries.length > 0) {
|
|
27173
|
-
const yearJournalPath = ensureYearJournalExists(directory, year);
|
|
27174
27240
|
const header = `; IBKR ${currency.toUpperCase()} investment transactions for ${year}
|
|
27175
27241
|
; Generated by opencode-accountant
|
|
27176
27242
|
; This file is auto-generated - do not edit manually`;
|
|
@@ -27181,13 +27247,6 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
|
|
|
27181
27247
|
}
|
|
27182
27248
|
fs22.writeFileSync(journalPath, journalContent);
|
|
27183
27249
|
journalFilePath = journalPath;
|
|
27184
|
-
const yearJournalContent = fs22.readFileSync(yearJournalPath, "utf-8");
|
|
27185
|
-
const includeDirective = `include investments/${path17.basename(journalPath)}`;
|
|
27186
|
-
if (!yearJournalContent.includes(includeDirective)) {
|
|
27187
|
-
fs22.writeFileSync(yearJournalPath, yearJournalContent.trimEnd() + `
|
|
27188
|
-
` + includeDirective + `
|
|
27189
|
-
`);
|
|
27190
|
-
}
|
|
27191
27250
|
if (tradedSymbols.size > 0) {
|
|
27192
27251
|
const commodityJournalPath = path17.join(directory, "ledger", "investments", "commodities.journal");
|
|
27193
27252
|
ensureCommodityDeclarations(commodityJournalPath, tradedSymbols, logger);
|
|
@@ -27232,7 +27291,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
|
|
|
27232
27291
|
trades: trades.length,
|
|
27233
27292
|
dividends: dividendCount,
|
|
27234
27293
|
adjustments: adjustments.length,
|
|
27235
|
-
|
|
27294
|
+
forex: forexTxns.length
|
|
27236
27295
|
},
|
|
27237
27296
|
alreadyPreprocessed: false
|
|
27238
27297
|
};
|
|
@@ -27611,7 +27670,7 @@ async function executeIbkrPreprocessStep(context, contextIds, logger) {
|
|
|
27611
27670
|
trades: 0,
|
|
27612
27671
|
dividends: 0,
|
|
27613
27672
|
adjustments: 0,
|
|
27614
|
-
|
|
27673
|
+
forex: 0
|
|
27615
27674
|
};
|
|
27616
27675
|
let lastJournalFile = null;
|
|
27617
27676
|
ibkrContexts.sort((a, b) => {
|
|
@@ -27627,7 +27686,7 @@ async function executeIbkrPreprocessStep(context, contextIds, logger) {
|
|
|
27627
27686
|
totalStats.trades += result.stats.trades;
|
|
27628
27687
|
totalStats.dividends += result.stats.dividends;
|
|
27629
27688
|
totalStats.adjustments += result.stats.adjustments;
|
|
27630
|
-
totalStats.
|
|
27689
|
+
totalStats.forex += result.stats.forex;
|
|
27631
27690
|
if (result.journalFile) {
|
|
27632
27691
|
lastJournalFile = result.journalFile;
|
|
27633
27692
|
}
|
|
@@ -27637,9 +27696,9 @@ async function executeIbkrPreprocessStep(context, contextIds, logger) {
|
|
|
27637
27696
|
});
|
|
27638
27697
|
logger?.info(`Updated context ${ibkrCtx.contextId} to use filtered CSV: ${path18.basename(result.simpleTransactionsCsv)}`);
|
|
27639
27698
|
}
|
|
27640
|
-
logger?.logStep("IBKR Preprocess", "success", `Processed: ${result.stats.trades} trades, ${result.stats.dividends} dividends, ${result.stats.adjustments} adjustments, ${result.stats.
|
|
27699
|
+
logger?.logStep("IBKR Preprocess", "success", `Processed: ${result.stats.trades} trades, ${result.stats.dividends} dividends, ${result.stats.adjustments} adjustments, ${result.stats.forex} forex`);
|
|
27641
27700
|
}
|
|
27642
|
-
const message = `Preprocessed ${totalStats.totalRows} rows: ${totalStats.trades} trades, ${totalStats.dividends} dividends, ${totalStats.adjustments} adjustments, ${totalStats.deposits} deposits, ${totalStats.
|
|
27701
|
+
const message = `Preprocessed ${totalStats.totalRows} rows: ${totalStats.trades} trades, ${totalStats.dividends} dividends, ${totalStats.adjustments} adjustments, ${totalStats.deposits} deposits, ${totalStats.forex} forex`;
|
|
27643
27702
|
context.result.steps.ibkrPreprocess = buildStepResult(true, message, {
|
|
27644
27703
|
...totalStats,
|
|
27645
27704
|
journalFile: lastJournalFile
|