Browse Source

fix: handle missing exchange rates gracefully instead of crashing

When exchange rate data is unavailable (e.g. on a fresh install before
the data gathering job completes), toCurrencyAtDate() returned undefined.
This propagated into new Big(undefined) in PortfolioCalculator and into
number accumulations in PortfolioService, crashing the Overview, Portfolio
and Holdings pages with "[big.js] Invalid number".

Return 0 instead of undefined from toCurrencyAtDate() when no exchange
rate can be found, consistent with the existing pattern in #6397. Add
defensive ?? 0 guards at call sites in ActivitiesService and
PortfolioService for additional safety.

The error log is preserved so missing exchange rates are still flagged
in the server logs without crashing the frontend.

Fixes #6482
pull/6574/head
h1net 3 weeks ago
parent
commit
49a07ea763
  1. 54
      apps/api/src/app/activities/activities.service.ts
  2. 8
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 2
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

54
apps/api/src/app/activities/activities.service.ts

@ -702,32 +702,34 @@ export class ActivitiesService {
feeInBaseCurrency, feeInBaseCurrency,
unitPriceInAssetProfileCurrency, unitPriceInAssetProfileCurrency,
valueInBaseCurrency valueInBaseCurrency
] = await Promise.all([ ] = (
this.exchangeRateDataService.toCurrencyAtDate( await Promise.all([
order.fee, this.exchangeRateDataService.toCurrencyAtDate(
order.currency ?? order.SymbolProfile.currency, order.fee,
order.SymbolProfile.currency, order.currency ?? order.SymbolProfile.currency,
order.date order.SymbolProfile.currency,
), order.date
this.exchangeRateDataService.toCurrencyAtDate( ),
order.fee, this.exchangeRateDataService.toCurrencyAtDate(
order.currency ?? order.SymbolProfile.currency, order.fee,
userCurrency, order.currency ?? order.SymbolProfile.currency,
order.date userCurrency,
), order.date
this.exchangeRateDataService.toCurrencyAtDate( ),
order.unitPrice, this.exchangeRateDataService.toCurrencyAtDate(
order.currency ?? order.SymbolProfile.currency, order.unitPrice,
order.SymbolProfile.currency, order.currency ?? order.SymbolProfile.currency,
order.date order.SymbolProfile.currency,
), order.date
this.exchangeRateDataService.toCurrencyAtDate( ),
value, this.exchangeRateDataService.toCurrencyAtDate(
order.currency ?? order.SymbolProfile.currency, value,
userCurrency, order.currency ?? order.SymbolProfile.currency,
order.date userCurrency,
) order.date
]); )
])
).map((result) => result ?? 0);
return { return {
...order, ...order,

8
apps/api/src/app/portfolio/portfolio.service.ts

@ -195,21 +195,21 @@ export class PortfolioService {
switch (type) { switch (type) {
case ActivityType.DIVIDEND: case ActivityType.DIVIDEND:
dividendInBaseCurrency += dividendInBaseCurrency +=
await this.exchangeRateDataService.toCurrencyAtDate( (await this.exchangeRateDataService.toCurrencyAtDate(
new Big(quantity).mul(unitPrice).toNumber(), new Big(quantity).mul(unitPrice).toNumber(),
currency ?? SymbolProfile.currency, currency ?? SymbolProfile.currency,
userCurrency, userCurrency,
date date
); )) ?? 0;
break; break;
case ActivityType.INTEREST: case ActivityType.INTEREST:
interestInBaseCurrency += interestInBaseCurrency +=
await this.exchangeRateDataService.toCurrencyAtDate( (await this.exchangeRateDataService.toCurrencyAtDate(
unitPrice, unitPrice,
currency ?? SymbolProfile.currency, currency ?? SymbolProfile.currency,
userCurrency, userCurrency,
date date
); )) ?? 0;
break; break;
} }

2
apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

@ -349,7 +349,7 @@ export class ExchangeRateDataService {
'ExchangeRateDataService' 'ExchangeRateDataService'
); );
return undefined; return 0;
} }
private async getExchangeRates({ private async getExchangeRates({

Loading…
Cancel
Save