Skip to content

Commit

Permalink
fix conversion issues
Browse files Browse the repository at this point in the history
  • Loading branch information
mkobetic committed Dec 18, 2024
1 parent 743848c commit 4ca4c58
Show file tree
Hide file tree
Showing 10 changed files with 1,284 additions and 419 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
},
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true
"files.trimTrailingWhitespace": true,
"makefile.configureOnOpen": false
}
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
### coin2html

- make commodity conversions more robust, they blow up too easily
- chart shows plain numeric values regardless of commodity!
- replace dateToString with d3.format
- tooltips for columns, inputs and wherever useful
- show details of selected posting
Expand Down
76 changes: 71 additions & 5 deletions cmd/coin2html/js/spec/commodity.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { Amount, Commodity } from "../src/commodity";
import {
Amount,
Commodities,
commodity,
Commodity,
composeConversions,
newConversion,
Price,
} from "../src/commodity";

test("create commodity", () =>
expect(new Commodity("CAD", "Canadian Dollar", 2, "")).toBeTruthy());
for (const [id, decimals] of Object.entries({
USD: 2,
CAD: 2,
EUR: 2,
}))
if (!Commodities[id]) Commodities[id] = new Commodity(id, id, decimals, "");

describe("amount", () => {
const CAD = new Commodity("CAD", "Canadian Dollar", 2, "");
const CAD = commodity`CAD`;
test.each([
[0, "0.00 CAD"],
[1, "0.01 CAD"],
Expand All @@ -13,8 +25,62 @@ describe("amount", () => {
[-50, "-0.50 CAD"],
[123456789, "1,234,567.89 CAD"],
[-12345678, "-123,456.78 CAD"],
])(`%#: %i`, (i, expected) => {
])(`%#: toString %i`, (i, expected) => {
const amt = new Amount(i, CAD);
expect(amt.toString()).toBe(expected);
});

test.each([
["25.00 CAD", "25.00 CAD"],
["25.0 CAD", "25.00 CAD"],
["25 CAD", "25.00 CAD"],
["-25 CAD", "-25.00 CAD"],
["0.1 CAD", "0.10 CAD"],
["-0.02834 CAD", "-0.02 CAD"],
["25.0330 CAD", "25.03 CAD"],
["25,033.5 CAD", "25,033.50 CAD"],
])(`%#: parse %s`, (input, expected) => {
expect(Amount.parse(input).toString()).toBe(expected);
});

test.each([
["4 CAD", 4, 2500],
["0.25 CAD", 4, 40000],
["4 CAD", 2, 25],
["0.25 CAD", 2, 400],
])(`%#: reciprocal %s/%d`, (input, decimals, expected) => {
expect(Amount.parse(input).reciprocal(decimals)).toBe(expected);
});
});

describe("price", () => {
test.each([
["CAD: 0.75 USD @ 2000-01-01"],
["USD: 1.33 CAD @ 2000-01-01"],
["USD: 0.99 EUR @ 2010-01-01"],
])("toString %s", (input) => {
const price = Price.parse(input);
expect(price.toString()).toBe(input);
});
test.each([
["CAD: 0.75 USD", "USD: 1.33 CAD"],
["USD: 1.33 CAD", "CAD: 0.75 USD"],
])("reverse %s", (input, expected) => {
const price = Price.parse(input);
const reversed = price.reverse().toString().split("@")[0].trim();
expect(reversed).toBe(expected);
});
});

describe("conversions", () => {
test("composition", () => {
const day = new Date("2000-01-01");
const dayString = day.toISOString().split("T")[0];
const cu = newConversion([Price.parse(`CAD: 0.75 USD @ ${dayString}`)]);
const ue = newConversion([Price.parse(`USD: 0.90 EUR @ ${dayString}`)]);
const ce = composeConversions(cu, ue);
expect(ce.direction).toBe("CAD => USD => EUR");
expect(ce(day).toString()).toBe("CAD: 0.68 EUR @ 2000-01-01");
expect(() => composeConversions(ue, cu)).toThrow();
});
});
20 changes: 11 additions & 9 deletions cmd/coin2html/js/src/account.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Amount, Commodities, Commodity } from "./commodity";
import { Amount, Commodity } from "./commodity";
import { State } from "./views";
import {
AccountPostingGroups,
Expand All @@ -7,6 +7,10 @@ import {
trimToDateRange,
} from "./utils";

/**
* Account, Posting and Transaction
*/

export class Account {
children: Account[] = [];
postings: Posting[] = [];
Expand Down Expand Up @@ -166,17 +170,15 @@ export let MaxDate = new Date(0);

export const Accounts: Record<string, Account> = {};
export const Roots: Account[] = [];
export function loadAccounts() {
const importedAccounts = JSON.parse(
document.getElementById("importedAccounts")!.innerText
) as importedAccounts;
export function loadAccounts(source: string) {
const importedAccounts = JSON.parse(source) as importedAccounts;
for (const impAccount of Object.values(importedAccounts)) {
if (impAccount.name == "Root") continue;
const parent = Accounts[impAccount.parent];
const account = new Account(
impAccount.name,
impAccount.fullName,
Commodities[impAccount.commodity],
Commodity.find(impAccount.commodity),
parent,
impAccount.location,
impAccount.closed ? new Date(impAccount.closed) : undefined
Expand All @@ -186,10 +188,10 @@ export function loadAccounts() {
Roots.push(account);
}
}
}

const importedTransactions = JSON.parse(
document.getElementById("importedTransactions")!.innerText
) as importedTransactions;
export function loadTransactions(source: string) {
const importedTransactions = JSON.parse(source) as importedTransactions;
for (const impTransaction of Object.values(importedTransactions)) {
const posted = new Date(impTransaction.posted);
if (posted < MinDate) MinDate = posted;
Expand Down
4 changes: 2 additions & 2 deletions cmd/coin2html/js/src/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
AggregationStyle,
addAggregationStyleInput,
} from "./views";
import { groupWithSubAccounts, PostingGroup } from "./utils";
import { groupByWithSubAccounts, PostingGroup } from "./utils";
import { Account } from "./account";
import { axisLeft, axisTop } from "d3-axis";
import { scaleLinear, scaleOrdinal, scaleTime } from "d3-scale";
Expand All @@ -33,7 +33,7 @@ export function viewChart(options?: {
const groupKey = Aggregation[State.View.Aggregate] as d3.TimeInterval;
const dates = groupKey.range(State.StartDate, State.EndDate);
const maxAccounts = State.View.AggregatedSubAccountMax;
const accountGroups = groupWithSubAccounts(account, groupKey, maxAccounts, {
const accountGroups = groupByWithSubAccounts(account, groupKey, maxAccounts, {
negated: opts.negated,
});
const labelFromAccount = (a: Account | undefined) =>
Expand Down
Loading

0 comments on commit 4ca4c58

Please sign in to comment.