@fuzzle/opencode-accountant 0.13.15-next.1 → 0.13.16-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 +120 -64
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25306,31 +25306,41 @@ function generateSplitEntry(action, oldQuantity, newQuantity, totalCostBasis, cu
25306
25306
  logger?.debug(`Generating ${splitType} entry: ${oldQuantity} -> ${newQuantity} ${action.symbol}`);
25307
25307
  const commodity = formatCommodity(action.symbol);
25308
25308
  const totalCost = formatAmount2(totalCostBasis, currency);
25309
- const postings = [
25309
+ const conversionAccount = `equity:conversion:${currency.toLowerCase()}`;
25310
+ const outgoingPostings = [
25310
25311
  {
25311
25312
  account: `assets:investments:stocks:${action.symbol}`,
25312
25313
  amount: `-${formatQuantity(oldQuantity)} ${commodity} @@ ${totalCost}`
25313
25314
  },
25314
25315
  {
25315
- account: "equity:conversion",
25316
- amount: `${formatQuantity(oldQuantity)} ${commodity} @@ ${totalCost}`
25317
- },
25316
+ account: conversionAccount,
25317
+ amount: totalCost
25318
+ }
25319
+ ];
25320
+ const incomingPostings = [
25318
25321
  {
25319
- account: "equity:conversion",
25320
- amount: `-${formatQuantity(newQuantity)} ${commodity} @@ ${totalCost}`
25322
+ account: conversionAccount,
25323
+ amount: formatAmount2(-totalCostBasis, currency)
25321
25324
  },
25322
25325
  {
25323
25326
  account: `assets:investments:stocks:${action.symbol}`,
25324
25327
  amount: `${formatQuantity(newQuantity)} ${commodity} @@ ${totalCost}`
25325
25328
  }
25326
25329
  ];
25330
+ const metadata = ` ; swissquote:order:${action.orderNum} isin:${action.isin}
25331
+ ` + ` ; Ratio: ${ratio.toFixed(4)} (${oldQuantity} -> ${newQuantity})
25332
+ `;
25327
25333
  let entry = `${date5} ${description}
25328
25334
  `;
25329
- entry += ` ; swissquote:order:${action.orderNum} isin:${action.isin}
25335
+ entry += metadata;
25336
+ entry += formatPostings(outgoingPostings) + `
25330
25337
  `;
25331
- entry += ` ; Ratio: ${ratio.toFixed(4)} (${oldQuantity} -> ${newQuantity})
25338
+ entry += `
25332
25339
  `;
25333
- entry += formatPostings(postings) + `
25340
+ entry += `${date5} ${description}
25341
+ `;
25342
+ entry += metadata;
25343
+ entry += formatPostings(incomingPostings) + `
25334
25344
  `;
25335
25345
  return entry;
25336
25346
  }
@@ -25371,43 +25381,89 @@ function generateMultiWayMergerEntry(group, crossCurrencyOutgoingSymbols, crossC
25371
25381
  logger?.debug(`Generating multi-way merger entry: ${outSymbols.join(", ")} -> ${inSymbols.join(", ")}`);
25372
25382
  const oldIsins = (crossCurrencyOutgoingIsins ?? group.outgoing.map((a) => a.isin)).filter(Boolean);
25373
25383
  const newIsins = group.incoming.map((a) => a.isin).filter(Boolean);
25374
- const postings = [];
25384
+ let metadata = ` ; swissquote:order:${group.orderNum}
25385
+ `;
25386
+ if (oldIsins.length > 0 || newIsins.length > 0) {
25387
+ metadata += ` ; Old ISIN: ${oldIsins.join(", ") || "n/a"}, New ISINs: ${newIsins.join(", ") || "n/a"}
25388
+ `;
25389
+ }
25390
+ const entries = [];
25375
25391
  for (let i2 = 0;i2 < group.outgoing.length; i2++) {
25376
25392
  const out = group.outgoing[i2];
25377
25393
  const qty = formatQuantity(Math.abs(out.quantity));
25378
25394
  const commodity = formatCommodity(out.symbol);
25379
- const costAnnotation = costInfo && costInfo.outgoingTotalCosts[i2] != null ? ` @@ ${formatAmount2(costInfo.outgoingTotalCosts[i2], costInfo.currency)}` : "";
25380
- postings.push({
25381
- account: `assets:investments:stocks:${out.symbol}`,
25382
- amount: `-${qty} ${commodity}${costAnnotation}`
25383
- });
25384
- postings.push({ account: "equity:conversion", amount: `${qty} ${commodity}${costAnnotation}` });
25395
+ if (costInfo && costInfo.outgoingTotalCosts[i2] != null) {
25396
+ const costAmount = formatAmount2(costInfo.outgoingTotalCosts[i2], costInfo.currency);
25397
+ const postings = [
25398
+ {
25399
+ account: `assets:investments:stocks:${out.symbol}`,
25400
+ amount: `-${qty} ${commodity} @@ ${costAmount}`
25401
+ },
25402
+ {
25403
+ account: `equity:conversion:${costInfo.currency.toLowerCase()}`,
25404
+ amount: costAmount
25405
+ }
25406
+ ];
25407
+ let sub = `${date5} ${description}
25408
+ `;
25409
+ sub += metadata;
25410
+ sub += formatPostings(postings) + `
25411
+ `;
25412
+ entries.push(sub);
25413
+ } else {
25414
+ const postings = [
25415
+ {
25416
+ account: `assets:investments:stocks:${out.symbol}`,
25417
+ amount: `-${qty} ${commodity}`
25418
+ }
25419
+ ];
25420
+ let sub = `${date5} ${description}
25421
+ `;
25422
+ sub += metadata;
25423
+ sub += formatPostings(postings) + `
25424
+ `;
25425
+ entries.push(sub);
25426
+ }
25385
25427
  }
25386
25428
  for (let i2 = 0;i2 < group.incoming.length; i2++) {
25387
25429
  const inc = group.incoming[i2];
25388
25430
  const qty = formatQuantity(Math.abs(inc.quantity));
25389
25431
  const commodity = formatCommodity(inc.symbol);
25390
- const costAnnotation = costInfo && costInfo.incomingTotalCosts[i2] != null ? ` @@ ${formatAmount2(costInfo.incomingTotalCosts[i2], costInfo.currency)}` : "";
25391
- postings.push({
25392
- account: "equity:conversion",
25393
- amount: `-${qty} ${commodity}${costAnnotation}`
25394
- });
25395
- postings.push({
25396
- account: `assets:investments:stocks:${inc.symbol}`,
25397
- amount: `${qty} ${commodity}${costAnnotation}`
25398
- });
25399
- }
25400
- let entry = `${date5} ${description}
25432
+ if (costInfo && costInfo.incomingTotalCosts[i2] != null) {
25433
+ const costAmount = formatAmount2(costInfo.incomingTotalCosts[i2], costInfo.currency);
25434
+ const postings = [
25435
+ {
25436
+ account: `equity:conversion:${costInfo.currency.toLowerCase()}`,
25437
+ amount: formatAmount2(-costInfo.incomingTotalCosts[i2], costInfo.currency)
25438
+ },
25439
+ {
25440
+ account: `assets:investments:stocks:${inc.symbol}`,
25441
+ amount: `${qty} ${commodity} @@ ${costAmount}`
25442
+ }
25443
+ ];
25444
+ let sub = `${date5} ${description}
25401
25445
  `;
25402
- entry += ` ; swissquote:order:${group.orderNum}
25446
+ sub += metadata;
25447
+ sub += formatPostings(postings) + `
25403
25448
  `;
25404
- if (oldIsins.length > 0 || newIsins.length > 0) {
25405
- entry += ` ; Old ISIN: ${oldIsins.join(", ") || "n/a"}, New ISINs: ${newIsins.join(", ") || "n/a"}
25449
+ entries.push(sub);
25450
+ } else {
25451
+ const postings = [
25452
+ {
25453
+ account: `assets:investments:stocks:${inc.symbol}`,
25454
+ amount: `${qty} ${commodity}`
25455
+ }
25456
+ ];
25457
+ let sub = `${date5} ${description}
25406
25458
  `;
25407
- }
25408
- entry += formatPostings(postings) + `
25459
+ sub += metadata;
25460
+ sub += formatPostings(postings) + `
25409
25461
  `;
25410
- return entry;
25462
+ entries.push(sub);
25463
+ }
25464
+ }
25465
+ return entries.join(`
25466
+ `);
25411
25467
  }
25412
25468
  function generateRightsDistributionEntry(action, logger) {
25413
25469
  const date5 = formatDate(action.date);
@@ -25430,28 +25486,23 @@ function generateRightsDistributionEntry(action, logger) {
25430
25486
  `;
25431
25487
  return entry;
25432
25488
  }
25433
- function generatePendingMergerWorthlessEntry(date5, orderNum, outgoingSymbols, outgoingIsins, outgoingQuantities, totalCostBasis, currency, logger) {
25489
+ function generateDirectWorthlessEntry(date5, orderNum, outgoingSymbols, outgoingIsins, outgoingQuantities, outgoingTotalCosts, totalCostBasis, currency, logger) {
25434
25490
  const formattedDate = formatDate(date5);
25435
25491
  const symbols = outgoingSymbols.join(" + ");
25436
- const description = escapeDescription(`Worthless: ${symbols} (unresolved merger)`);
25437
- logger?.debug(`Generating pending merger worthless entry: ${symbols}, loss: ${totalCostBasis.toFixed(2)} ${currency}`);
25492
+ const description = escapeDescription(`Worthless liquidation: ${symbols}`);
25493
+ logger?.debug(`Generating direct worthless entry: ${symbols}, loss: ${totalCostBasis.toFixed(2)} ${currency}`);
25438
25494
  const postings = [];
25439
- if (outgoingQuantities.length > 0) {
25440
- const totalQty = outgoingQuantities.reduce((a, b) => a + b, 0);
25441
- for (let i2 = 0;i2 < outgoingSymbols.length; i2++) {
25442
- const symbol2 = outgoingSymbols[i2];
25443
- const quantity = outgoingQuantities[i2] || 0;
25444
- if (quantity <= 0)
25445
- continue;
25446
- const proportion = totalQty > 0 ? quantity / totalQty : 0;
25447
- const costForSymbol = totalCostBasis * proportion;
25448
- const costBasisPerUnit = costForSymbol / quantity;
25449
- const commodity = formatCommodity(symbol2);
25450
- postings.push({
25451
- account: "equity:conversion",
25452
- amount: `-${formatQuantity(quantity)} ${commodity} @ ${formatPrice(costBasisPerUnit)} ${currency}`
25453
- });
25454
- }
25495
+ for (let i2 = 0;i2 < outgoingSymbols.length; i2++) {
25496
+ const symbol2 = outgoingSymbols[i2];
25497
+ const quantity = outgoingQuantities[i2] || 0;
25498
+ const cost = outgoingTotalCosts[i2] || 0;
25499
+ if (quantity <= 0)
25500
+ continue;
25501
+ const commodity = formatCommodity(symbol2);
25502
+ postings.push({
25503
+ account: `assets:investments:stocks:${symbol2}`,
25504
+ amount: `-${formatQuantity(quantity)} ${commodity} @@ ${formatAmount2(cost, currency)}`
25505
+ });
25455
25506
  }
25456
25507
  postings.push({
25457
25508
  account: "expenses:losses:capital",
@@ -25461,7 +25512,7 @@ function generatePendingMergerWorthlessEntry(date5, orderNum, outgoingSymbols, o
25461
25512
  `;
25462
25513
  entry += ` ; swissquote:order:${orderNum}
25463
25514
  `;
25464
- entry += ` ; Pending merger resolved as worthless - total loss: ${totalCostBasis.toFixed(2)} ${currency}
25515
+ entry += ` ; total loss: ${totalCostBasis.toFixed(2)} ${currency}
25465
25516
  `;
25466
25517
  if (outgoingIsins.length > 0) {
25467
25518
  entry += ` ; Original ISINs: ${outgoingIsins.join(", ")}
@@ -25699,6 +25750,17 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
25699
25750
  if (group.outgoing.length === 0 && group.incoming.length > 0) {
25700
25751
  const pendingState = loadPendingMerger(projectDir, lotInventoryPath, group.key, logger);
25701
25752
  if (pendingState) {
25753
+ for (let i2 = 0;i2 < pendingState.outgoingSymbols.length; i2++) {
25754
+ group.outgoing.push({
25755
+ date: pendingState.date,
25756
+ orderNum: pendingState.orderNum,
25757
+ type: "Internal exchange of securities",
25758
+ symbol: pendingState.outgoingSymbols[i2],
25759
+ name: "",
25760
+ isin: pendingState.outgoingIsins[i2] || "",
25761
+ quantity: -(pendingState.outgoingQuantities[i2] || 0)
25762
+ });
25763
+ }
25702
25764
  const totalIncomingQty2 = group.incoming.reduce((sum, a) => sum + Math.abs(a.quantity), 0);
25703
25765
  const incomingTotalCosts2 = [];
25704
25766
  for (const inc of group.incoming) {
@@ -25722,11 +25784,11 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
25722
25784
  logger?.info(`Cross-currency merger incoming: ${absQty} ${inc.symbol} @ ${costBasisPerUnit.toFixed(2)} ${pendingState.currency}`);
25723
25785
  }
25724
25786
  const costInfo2 = {
25725
- outgoingTotalCosts: [],
25787
+ outgoingTotalCosts: pendingState.outgoingTotalCosts || [],
25726
25788
  incomingTotalCosts: incomingTotalCosts2,
25727
25789
  currency: pendingState.currency
25728
25790
  };
25729
- const entry2 = generateMultiWayMergerEntry(group, pendingState.outgoingSymbols, pendingState.outgoingIsins, costInfo2, logger);
25791
+ const entry2 = generateMultiWayMergerEntry(group, undefined, undefined, costInfo2, logger);
25730
25792
  entries.push(entry2);
25731
25793
  removePendingMerger(projectDir, lotInventoryPath, group.key, logger);
25732
25794
  } else {
@@ -25764,16 +25826,10 @@ function processMultiWayMerger(group, inventory, lotInventoryPath, projectDir, l
25764
25826
  outgoingSymbols,
25765
25827
  outgoingIsins: group.outgoing.map((a) => a.isin),
25766
25828
  outgoingQuantities: group.outgoing.map((a) => Math.abs(a.quantity)),
25829
+ outgoingTotalCosts,
25767
25830
  totalCostBasis,
25768
25831
  currency: costCurrency
25769
25832
  }, logger);
25770
- const costInfo2 = {
25771
- outgoingTotalCosts,
25772
- incomingTotalCosts: [],
25773
- currency: costCurrency
25774
- };
25775
- const entry2 = generateMultiWayMergerEntry(group, undefined, undefined, costInfo2, logger);
25776
- entries.push(entry2);
25777
25833
  logger?.info(`Cross-currency merger outgoing: ${outgoingSymbols.join(", ")} -> pending (cost basis: ${totalCostBasis.toFixed(2)})`);
25778
25834
  return entries;
25779
25835
  }
@@ -25871,7 +25927,7 @@ function resolveRemainingPendingMergers(projectDir, lotInventoryPath, logger) {
25871
25927
  continue;
25872
25928
  }
25873
25929
  const year = parseInt(dateMatch[1], 10);
25874
- const entry = generatePendingMergerWorthlessEntry(state.date, state.orderNum, state.outgoingSymbols, state.outgoingIsins, state.outgoingQuantities || [], state.totalCostBasis, state.currency, logger);
25930
+ const entry = generateDirectWorthlessEntry(state.date, state.orderNum, state.outgoingSymbols, state.outgoingIsins, state.outgoingQuantities || [], state.outgoingTotalCosts || [], state.totalCostBasis, state.currency, logger);
25875
25931
  const journalFile = path13.join(projectDir, "ledger", "investments", `${year}-${state.currency.toLowerCase()}.journal`);
25876
25932
  if (!entriesByJournal.has(journalFile)) {
25877
25933
  entriesByJournal.set(journalFile, []);
@@ -26178,7 +26234,7 @@ async function preprocessSwissquote(csvPath, projectDir, currency, year, lotInve
26178
26234
  investmentAccounts.add("income:capital-gains:realized");
26179
26235
  investmentAccounts.add("income:capital-gains:rights-distribution");
26180
26236
  investmentAccounts.add("expenses:losses:capital");
26181
- investmentAccounts.add("equity:conversion");
26237
+ investmentAccounts.add(`equity:conversion:${currency.toLowerCase()}`);
26182
26238
  investmentAccounts.add("equity:rounding");
26183
26239
  ensureInvestmentAccountDeclarations(accountJournalPath, investmentAccounts, logger);
26184
26240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.13.15-next.1",
3
+ "version": "0.13.16-next.1",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",