Skip to content

PR #613: Currency system (UAH/USD/EUR) + NBU daily sync + user preferences#623

Merged
barach6662001-bit merged 6 commits intomainfrom
pr613-currency-system
Apr 24, 2026
Merged

PR #613: Currency system (UAH/USD/EUR) + NBU daily sync + user preferences#623
barach6662001-bit merged 6 commits intomainfrom
pr613-currency-system

Conversation

@barach6662001-bit
Copy link
Copy Markdown
Owner

Summary

Implements the Currency system per docs/ROADMAP.md → In progress and docs/TZ.md 8.2.

Backend

  • New domain entities:
    • ExchangeRate (Code, Date, RateToUah, FetchedAtUtc) with composite PK {Code, Date} — global (non-tenant-scoped) table.
    • UserPreferences (UserId PK = FK to AspNetUsers, PreferredCurrency default UAH, UpdatedAtUtc).
  • EF Core migration AddCurrencyAndUserPreferences.
  • INbuCurrencyService + NbuCurrencyService:
    • NBU endpoint https://bank.gov.ua/NBU_Exchange/exchange_site with valcode, start, end, json.
    • Supported codes: USD, EUR.
    • Methods: GetRateAsync (with previous-business-day fallback + last-stored-rate fallback on NBU failure), GetLatestRateAsync, SyncDailyAsync, BackfillAsync.
  • NbuDailySyncJob — BackgroundService, runs daily at 06:00 Europe/Kyiv (falls back to Europe/Kiev, then fixed +02:00 on systems without tzdata).
  • DI: AddHttpClient<INbuCurrencyService, NbuCurrencyService> (15s timeout, UA header) + AddHostedService<NbuDailySyncJob>.
  • New CurrencyController with 4 endpoints:
    • GET /api/currency/rates/latest — latest stored USD+EUR rates.
    • GET /api/currency/rates?code=&date= — rate for code/date with previous-business-day fallback; UAH short-circuits to 1.
    • GET /api/currency/preferences — current user's preferred display currency (UAH default).
    • PUT /api/currency/preferences — update preferred currency (UAH/USD/EUR only).
  • MeController extended with preferredCurrency.

Frontend

  • api/currency.tsgetLatestRates, getPreferences, updatePreferences.
  • stores/currencyStore.ts — Zustand store for preferred currency + cached USD/EUR rates.
  • hooks/useFormatCurrency.ts — formats a UAH amount into the user's preferred currency via Intl.NumberFormat('uk-UA'); gracefully falls back to UAH if the target rate is unknown.
  • Profile page → Валюта selector (UAH/USD/EUR).
  • i18n strings added to both uk.ts and en.ts.

Tests

  • Unit: 6 tests for NbuCurrencyService (JSON parse, upsert, weekend fallback, UAH shortcut, TimeUntilNext06Kyiv, backfill window) using FakeHttpHandler + in-memory TestDbContext. All passing.
  • Integration: 5 tests for /api/currency/* endpoints (latest rates dedup, previous-business-day fallback, UAH=1, preference persistence, invalid code → 400). Tests use distinct future-year dates to avoid PK collisions in the shared Testcontainers DB and seed an AppUser for the FK. All passing.

Locked decisions followed

  • Base currency is UAH in DB; conversion at presentation only.
  • ExchangeRate has composite PK (Code, Date).
  • Weekend/holiday → previous business day row.
  • NBU unreachable → last stored rate.
  • No rate_at_transaction added to operation tables.

Verification

  • dotnet test435 / 435 passing (315 unit + 120 integration).
  • npx tsc --noEmit → clean.

Not in this PR

  • Export CSV/PDF currency header with NBU rate on export date — deferred to a follow-up (requires touching the existing export helpers, not yet tied to the NBU service in this PR).

Closes TZ 8.2 (to be marked after merge).

… endpoints

- Unit tests for NbuCurrencyService (JSON parse, upsert, backfill, UAH short-circuit, cron next-run helper)
- Integration tests for /api/currency/rates/latest, /api/currency/rates (business-day fallback, UAH=1), and /api/currency/preferences (valid/invalid)
- Rewrite GetLatestRates query as per-code FirstOrDefault for reliable EF translation
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3618aeca34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return;
}
if (get().loading) return;
if (get().loaded && get().loadedForTenantId === tenantId) return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Key currency cache by user identity

The load guard caches currency data at tenant scope, but preferredCurrency is user-specific. When user A logs out and user B (same tenant) logs in within the same SPA session, load() returns early and keeps user A’s preference, so profile/settings and any formatting based on this store can show the wrong currency for user B. Include user identity in the cache key (or force reset/refetch on auth user change) instead of only tenantId.

Useful? React with 👍 / 👎.

@barach6662001-bit barach6662001-bit merged commit 89c52c4 into main Apr 24, 2026
3 checks passed
@barach6662001-bit barach6662001-bit deleted the pr613-currency-system branch April 24, 2026 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant