@fuzzle/opencode-accountant 0.16.0-next.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +70 -9
  2. 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
- return parsed1.amount === parsed2.amount;
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
@@ -26968,6 +26969,34 @@ function generateIbkrAdjustmentEntry(adjustment, logger) {
26968
26969
  `;
26969
26970
  return entry;
26970
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
+ }
26971
27000
 
26972
27001
  // src/utils/ibkrCsvPreprocessor.ts
26973
27002
  var TRADE_TYPES2 = new Set(["Buy", "Sell"]);
@@ -27064,7 +27093,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
27064
27093
  trades: 0,
27065
27094
  dividends: 0,
27066
27095
  adjustments: 0,
27067
- skippedForex: 0
27096
+ forex: 0
27068
27097
  },
27069
27098
  alreadyPreprocessed: true
27070
27099
  };
@@ -27076,7 +27105,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
27076
27105
  const trades = [];
27077
27106
  const dividendTxns = [];
27078
27107
  const adjustments = [];
27079
- let skippedForex = 0;
27108
+ const forexTxns = [];
27080
27109
  for (const txn of transactions) {
27081
27110
  if (TRADE_TYPES2.has(txn.transactionType)) {
27082
27111
  trades.push(txn);
@@ -27087,7 +27116,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
27087
27116
  } else if (txn.transactionType === DEPOSIT_TYPE) {
27088
27117
  deposits.push(txn);
27089
27118
  } else if (txn.transactionType === FOREX_TYPE) {
27090
- skippedForex++;
27119
+ forexTxns.push(txn);
27091
27120
  } else {
27092
27121
  logger?.warn(`Unknown IBKR transaction type: ${txn.transactionType}`);
27093
27122
  }
@@ -27173,6 +27202,38 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
27173
27202
  usedAccounts.add("income:fx-gains:ibkr");
27174
27203
  }
27175
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
+ }
27176
27237
  saveLotInventory(directory, lotInventoryPath, inventory, logger);
27177
27238
  let journalFilePath = null;
27178
27239
  if (journalEntries.length > 0) {
@@ -27230,7 +27291,7 @@ async function preprocessIbkr(csvPath, directory, currency, year, lotInventoryPa
27230
27291
  trades: trades.length,
27231
27292
  dividends: dividendCount,
27232
27293
  adjustments: adjustments.length,
27233
- skippedForex
27294
+ forex: forexTxns.length
27234
27295
  },
27235
27296
  alreadyPreprocessed: false
27236
27297
  };
@@ -27609,7 +27670,7 @@ async function executeIbkrPreprocessStep(context, contextIds, logger) {
27609
27670
  trades: 0,
27610
27671
  dividends: 0,
27611
27672
  adjustments: 0,
27612
- skippedForex: 0
27673
+ forex: 0
27613
27674
  };
27614
27675
  let lastJournalFile = null;
27615
27676
  ibkrContexts.sort((a, b) => {
@@ -27625,7 +27686,7 @@ async function executeIbkrPreprocessStep(context, contextIds, logger) {
27625
27686
  totalStats.trades += result.stats.trades;
27626
27687
  totalStats.dividends += result.stats.dividends;
27627
27688
  totalStats.adjustments += result.stats.adjustments;
27628
- totalStats.skippedForex += result.stats.skippedForex;
27689
+ totalStats.forex += result.stats.forex;
27629
27690
  if (result.journalFile) {
27630
27691
  lastJournalFile = result.journalFile;
27631
27692
  }
@@ -27635,9 +27696,9 @@ async function executeIbkrPreprocessStep(context, contextIds, logger) {
27635
27696
  });
27636
27697
  logger?.info(`Updated context ${ibkrCtx.contextId} to use filtered CSV: ${path18.basename(result.simpleTransactionsCsv)}`);
27637
27698
  }
27638
- logger?.logStep("IBKR Preprocess", "success", `Processed: ${result.stats.trades} trades, ${result.stats.dividends} dividends, ${result.stats.adjustments} adjustments, ${result.stats.skippedForex} forex skipped`);
27699
+ logger?.logStep("IBKR Preprocess", "success", `Processed: ${result.stats.trades} trades, ${result.stats.dividends} dividends, ${result.stats.adjustments} adjustments, ${result.stats.forex} forex`);
27639
27700
  }
27640
- const message = `Preprocessed ${totalStats.totalRows} rows: ${totalStats.trades} trades, ${totalStats.dividends} dividends, ${totalStats.adjustments} adjustments, ${totalStats.deposits} deposits, ${totalStats.skippedForex} forex skipped`;
27701
+ const message = `Preprocessed ${totalStats.totalRows} rows: ${totalStats.trades} trades, ${totalStats.dividends} dividends, ${totalStats.adjustments} adjustments, ${totalStats.deposits} deposits, ${totalStats.forex} forex`;
27641
27702
  context.result.steps.ibkrPreprocess = buildStepResult(true, message, {
27642
27703
  ...totalStats,
27643
27704
  journalFile: lastJournalFile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.16.0-next.1",
3
+ "version": "0.16.1-next.1",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",