PR #613: Currency system (UAH/USD/EUR) + NBU daily sync + user preferences#623
PR #613: Currency system (UAH/USD/EUR) + NBU daily sync + user preferences#623barach6662001-bit merged 6 commits intomainfrom
Conversation
… 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
There was a problem hiding this comment.
💡 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; |
There was a problem hiding this comment.
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 👍 / 👎.
Summary
Implements the Currency system per
docs/ROADMAP.md→ In progress anddocs/TZ.md8.2.Backend
ExchangeRate(Code, Date, RateToUah, FetchedAtUtc) with composite PK{Code, Date}— global (non-tenant-scoped) table.UserPreferences(UserId PK = FK to AspNetUsers, PreferredCurrency defaultUAH, UpdatedAtUtc).AddCurrencyAndUserPreferences.INbuCurrencyService+NbuCurrencyService:https://bank.gov.ua/NBU_Exchange/exchange_sitewithvalcode,start,end,json.USD,EUR.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).AddHttpClient<INbuCurrencyService, NbuCurrencyService>(15s timeout, UA header) +AddHostedService<NbuDailySyncJob>.CurrencyControllerwith 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).MeControllerextended withpreferredCurrency.Frontend
api/currency.ts—getLatestRates,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 viaIntl.NumberFormat('uk-UA'); gracefully falls back to UAH if the target rate is unknown.uk.tsanden.ts.Tests
NbuCurrencyService(JSON parse, upsert, weekend fallback, UAH shortcut,TimeUntilNext06Kyiv, backfill window) usingFakeHttpHandler+ in-memoryTestDbContext. All passing./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 anAppUserfor the FK. All passing.Locked decisions followed
ExchangeRatehas composite PK(Code, Date).rate_at_transactionadded to operation tables.Verification
dotnet test→ 435 / 435 passing (315 unit + 120 integration).npx tsc --noEmit→ clean.Not in this PR
Closes TZ 8.2 (to be marked after merge).