wallet: never store a native figure as AUD; rate from statement rows only
ci / lint-test (push) Successful in 51s
ci / lint-test (push) Successful in 51s
A CommBank approval prompt for 672006 VND had no VND rate history, and the
no_rate_reference branch left the native figure in transactions.amount -- the
ledger carried A$672,006 for six days (txn 7316, repaired to 35.95).
- no rate history: amount = 0, still flagged aud_provisional/no_rate_reference;
reconcile matches on the native pair, so the statement supplies the AUD
- medianRateToAud reads statement rows only; a wallet row's amount is itself
a guess and a 1:1 row would have poisoned the next estimate
- foreign halves pair on the native pair, since several can share amount 0
- currencyFromAmountText reads ISO codes around the figure ("672006 VND")
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -208,6 +208,45 @@ describe("currency", () => {
|
||||
const [m] = await metas();
|
||||
// Says so, rather than inventing a rate.
|
||||
expect(m.flags).toContain("no_rate_reference");
|
||||
// And never stores the native figure as AUD (672006 VND became A$672,006).
|
||||
expect(Number(t.amount)).toBe(0);
|
||||
expect(Number(t.foreign_currency_amount)).toBe(42);
|
||||
});
|
||||
|
||||
it("derives the rate from statement rows only, never from wallet rows", async () => {
|
||||
// Six statementless rows at a nonsense 1:1 rate must not count as history.
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await pool.query(
|
||||
`INSERT INTO transactions (transaction_date, description, amount, transaction_type,
|
||||
owner_id, foreign_currency_amount, foreign_currency_code, source, source_ref)
|
||||
VALUES ('2026-01-10','VN',1000,'debit',$1,1000,'VND','wallet',$2)`,
|
||||
[ownerId, `evt:seed${i}`]
|
||||
);
|
||||
}
|
||||
await ingest(req({
|
||||
event_id: 997, kind: "transaction", direction: "debit", amount: 672006,
|
||||
amount_text: "672006 VND", currency: "VND", merchant: "EVISA VIETNAM", notif_app: "CommBank",
|
||||
occurred_at: "2026-09-05T11:22:00+10:00",
|
||||
}));
|
||||
const t = (await txns()).find((r) => r.source_ref === "evt:997")!;
|
||||
expect(Number(t.amount)).toBe(0);
|
||||
const [m] = (await metas()).filter((r) => r.source_message_id === "evt:997");
|
||||
expect(m.flags).toContain("no_rate_reference");
|
||||
});
|
||||
|
||||
it("pairs foreign halves on the native figure, not the placeholder amount", async () => {
|
||||
await ingest(req({
|
||||
event_id: 996, kind: "transaction", direction: "debit", amount: 42,
|
||||
amount_text: "€42.00", merchant: "Cafe A", notif_app: "Wallet",
|
||||
occurred_at: "2026-08-20T12:00:00+10:00",
|
||||
}));
|
||||
await ingest(req({
|
||||
event_id: 995, kind: "transaction", direction: "debit", amount: 17,
|
||||
amount_text: "€17.00", merchant: "Cafe B", notif_app: "Wallet",
|
||||
occurred_at: "2026-08-20T12:05:00+10:00",
|
||||
}));
|
||||
// Both have amount 0; they are different purchases and must stay two rows.
|
||||
expect((await txns()).filter((r) => r.foreign_currency_code === "EUR")).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user