currency: a wallet row's amount is AUD, so show its native figure, not the AUD one under a foreign label
ci / lint-test (push) Successful in 1m41s
ci / lint-test (push) Successful in 1m41s
Wallet rows store the provisional AUD figure in amount and amount_aud and the native figure in foreign_currency_amount. NATIVE_CURRENCY labelled every statement-less row with foreign_currency_code (the order-lane convention, where amount IS native and amount_aud is NULL), so an INR 524 tap rendered as "$9.42 / INR 9.42" (txn 8162). amount_aud being present now marks amount as AUD. Measured live: exactly the 38 foreign wallet rows change label, nothing else. The transactions list's second line prints foreign_currency_amount where one exists, which also surfaces the native figure on statement rows that carry one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,19 @@ describe("NATIVE_CURRENCY", () => {
|
|||||||
expect(unconverted).toBe(true);
|
expect(unconverted).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("labels a statement-less wallet row AUD, because its amount already is", async () => {
|
||||||
|
// Wallet rows store a provisional AUD figure in amount AND amount_aud, and
|
||||||
|
// the native figure in foreign_currency_amount. Reading the foreign code
|
||||||
|
// here printed "INR 9.42" for an INR 524 tap (txn 8162).
|
||||||
|
const id = await scratchTxn(
|
||||||
|
"transaction_date, description, amount, amount_aud, transaction_type, payment_method, source, source_ref, foreign_currency_amount, foreign_currency_code",
|
||||||
|
"'2026-10-03','Wallet foreign fixture', 9.42, 9.42, 'debit', 'card', 'wallet', 'evt:fixture-native-ccy', 524.00, 'INR'"
|
||||||
|
);
|
||||||
|
const { ccy, unconverted } = await currencyOf(id);
|
||||||
|
expect(ccy).toBe("AUD");
|
||||||
|
expect(unconverted).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("prefers the statement's currency over the foreign-charge record", async () => {
|
it("prefers the statement's currency over the foreign-charge record", async () => {
|
||||||
// Opposite convention: on an AUD statement, `amount` is AUD and
|
// Opposite convention: on an AUD statement, `amount` is AUD and
|
||||||
// foreign_currency_code merely notes what was originally charged. Reading
|
// foreign_currency_code merely notes what was originally charged. Reading
|
||||||
|
|||||||
@@ -1085,7 +1085,18 @@ function TransactionsContent() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{formatAmount(t.amount_aud ?? t.amount, t.transaction_type)}
|
{formatAmount(t.amount_aud ?? t.amount, t.transaction_type)}
|
||||||
{t.currency && t.currency !== "AUD" && (
|
{/*
|
||||||
|
The native figure: `foreign_currency_amount` where one
|
||||||
|
was recorded (a statement or wallet row, whose `amount`
|
||||||
|
is AUD), else `amount` on a non-AUD statement. Printing
|
||||||
|
`amount` under the foreign code put the AUD figure
|
||||||
|
behind an INR label on every wallet row.
|
||||||
|
*/}
|
||||||
|
{t.foreign_currency_code && t.foreign_currency_code !== "AUD" && t.foreign_currency_amount !== null ? (
|
||||||
|
<div className="text-[10px] text-zinc-500 mt-0.5">
|
||||||
|
{formatAmount(t.foreign_currency_amount, t.transaction_type, t.foreign_currency_code)}
|
||||||
|
</div>
|
||||||
|
) : t.currency && t.currency !== "AUD" && (
|
||||||
<div className="text-[10px] text-zinc-500 mt-0.5">
|
<div className="text-[10px] text-zinc-500 mt-0.5">
|
||||||
{formatAmount(t.amount, t.transaction_type, t.currency)}
|
{formatAmount(t.amount, t.transaction_type, t.currency)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,10 +89,17 @@ export const EXCLUDE_RECONCILED_SOURCE = `NOT (t.statement_id IS NULL AND t.reco
|
|||||||
* figure and `foreign_currency_code` names it (order-ingestion.ts leaves
|
* figure and `foreign_currency_code` names it (order-ingestion.ts leaves
|
||||||
* `amount_aud` NULL rather than asserting an FX rate it does not have).
|
* `amount_aud` NULL rather than asserting an FX rate it does not have).
|
||||||
*
|
*
|
||||||
|
* - A wallet row also has no statement, but follows the STATEMENT convention:
|
||||||
|
* `amount` = `amount_aud` = a provisional AUD figure, and the native figure
|
||||||
|
* lives in `foreign_currency_amount` (wallet-ingestion.ts). `amount_aud`
|
||||||
|
* being present is what says `amount` is already AUD. Without that arm, an
|
||||||
|
* INR 524 tap stored as A$9.42 rendered as "INR 9.42" -- the AUD number
|
||||||
|
* under the foreign label (txn 8162, 2026-10-03).
|
||||||
|
*
|
||||||
* Reading `s.currency` alone labels every foreign order row as AUD.
|
* Reading `s.currency` alone labels every foreign order row as AUD.
|
||||||
* `s` must be the statements alias in scope.
|
* `s` must be the statements alias in scope.
|
||||||
*/
|
*/
|
||||||
export const NATIVE_CURRENCY = `COALESCE(s.currency, t.foreign_currency_code, 'AUD')`;
|
export const NATIVE_CURRENCY = `COALESCE(s.currency, CASE WHEN t.amount_aud IS NULL THEN t.foreign_currency_code END, 'AUD')`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when a row's AUD value is unknown: it is denominated in something other
|
* True when a row's AUD value is unknown: it is denominated in something other
|
||||||
|
|||||||
Reference in New Issue
Block a user