mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(costs): read a foreign-currency expense at the rate it was booked at
The server freezes the FX rate when an expense or a settle-up transfer is entered and settles the balances with it, but the client ignored the column and converted every amount with today's rate. A bill paid in dollars was therefore worth something different each time it was opened, which a tester noticed after settling up: the figure moved under him, and the row no longer matched what had actually changed hands. convertBooked applies the server's own rule, in one place for both shells and the transfer rows: an amount already in the trip currency is left to the live step, a frozen rate converts to the trip currency before the live step, and a rate of 1 or none at all is a row written before the freeze existed and keeps converting live.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { http, HttpResponse } from 'msw'
|
||||
import { server } from '../../tests/helpers/msw/server'
|
||||
import { fetchExchangeRates, clearExchangeRateCache } from './useExchangeRates'
|
||||
import { convertBooked, fetchExchangeRates, clearExchangeRateCache } from './useExchangeRates'
|
||||
|
||||
const FX_URL = 'https://api.frankfurter.dev/v2/rates'
|
||||
|
||||
@@ -45,3 +45,51 @@ describe('fetchExchangeRates (#1561)', () => {
|
||||
expect(await fetchExchangeRates('NOK')).toEqual({ NOK: 1, USD: 0.1 })
|
||||
})
|
||||
})
|
||||
|
||||
// A cost entered in a foreign currency is money that was actually paid, at a rate that
|
||||
// was true that day. Reading it back at today's rate quietly rewrites history, which is
|
||||
// what a tester reported after settling up: the figure moved under him.
|
||||
describe('convertBooked', () => {
|
||||
// Display currency is EUR, so rates are units per 1 EUR. The trip is booked in EUR too
|
||||
// unless a test says otherwise.
|
||||
const live = (amount: number, from: string | null | undefined): number => {
|
||||
const rates: Record<string, number> = { EUR: 1, USD: 2, SEK: 10 }
|
||||
const r = rates[(from || 'EUR').toUpperCase()]
|
||||
return r && r > 0 ? amount / r : amount
|
||||
}
|
||||
|
||||
it('leaves an amount already in the trip currency to the live step alone', () => {
|
||||
// Nothing was ever frozen here, so this is just trip currency to display currency.
|
||||
expect(convertBooked(100, null, 1, 'EUR', live)).toBe(100)
|
||||
expect(convertBooked(100, 'EUR', 1, 'EUR', live)).toBe(100)
|
||||
})
|
||||
|
||||
it('reads a foreign amount at the rate it was booked at, not at today s', () => {
|
||||
// 120 USD booked when a euro bought 1.2 dollars is 100 EUR, and stays 100 EUR even
|
||||
// though the live table above says a euro now buys 2.
|
||||
expect(convertBooked(120, 'USD', 1.2, 'EUR', live)).toBe(100)
|
||||
})
|
||||
|
||||
it('falls back to live rates for a row that never froze one', () => {
|
||||
// Rate 1 is the column default, not a booked rate: rows predating the freeze carry it.
|
||||
expect(convertBooked(120, 'USD', 1, 'EUR', live)).toBe(60)
|
||||
expect(convertBooked(120, 'USD', undefined, 'EUR', live)).toBe(60)
|
||||
expect(convertBooked(120, 'USD', null, 'EUR', live)).toBe(60)
|
||||
// A rate that cannot divide is no rate at all.
|
||||
expect(convertBooked(120, 'USD', 0, 'EUR', live)).toBe(60)
|
||||
expect(convertBooked(120, 'USD', -2, 'EUR', live)).toBe(60)
|
||||
})
|
||||
|
||||
it('goes through the trip currency when the reader displays a third one', () => {
|
||||
// 200 SEK booked at 10 SEK per euro is 20 EUR of trip money, which the live step then
|
||||
// takes to the display currency. The frozen rate is against the trip, never the display.
|
||||
expect(convertBooked(200, 'SEK', 10, 'EUR', live)).toBe(20)
|
||||
// Trip in USD, display in EUR: 200 SEK at 5 SEK per dollar is 40 USD, then 20 EUR.
|
||||
expect(convertBooked(200, 'SEK', 5, 'USD', live)).toBe(20)
|
||||
})
|
||||
|
||||
it('treats currency case as noise', () => {
|
||||
expect(convertBooked(120, 'usd', 1.2, 'eur', live)).toBe(100)
|
||||
expect(convertBooked(100, 'eur', 1, 'EUR', live)).toBe(100)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user