diff --git a/apps/api/src/services/billmanager/context.ts b/apps/api/src/services/billmanager/context.ts index 56bc941..40248c5 100644 --- a/apps/api/src/services/billmanager/context.ts +++ b/apps/api/src/services/billmanager/context.ts @@ -6,6 +6,7 @@ type ProviderRow = typeof schema.providers.$inferSelect export interface BillmanagerSyncAccount extends AccountRow { apiType: 'billmanager' apiBaseUrl: string + providerBaseCurrency?: string | null } export function resolveBillmanagerApi( @@ -25,5 +26,5 @@ export function billmanagerAccountRowForSync( const { apiType, apiBaseUrl } = resolveBillmanagerApi(accountRow, providerRow) const cred = String(accountRow.apiCredentials || '').trim() if (apiType !== 'billmanager' || !apiBaseUrl || !cred) return null - return { ...accountRow, apiType: 'billmanager', apiBaseUrl } + return { ...accountRow, apiType: 'billmanager', apiBaseUrl, providerBaseCurrency: providerRow?.baseCurrency ?? null } } diff --git a/apps/api/src/services/billmanager/sync.ts b/apps/api/src/services/billmanager/sync.ts index dc79640..8e30767 100644 --- a/apps/api/src/services/billmanager/sync.ts +++ b/apps/api/src/services/billmanager/sync.ts @@ -6,6 +6,7 @@ import { and, eq, like, or } from 'drizzle-orm' import { getDb, schema } from '@cfdm/db' import type { BillmanagerSyncAccount } from './context.js' +import { syncFallbackCurrency } from '@cfdm/shared/utils/account-balance' import { mapPaymentToPayment, mapVdsToVps } from './mappers.js' import { fetchDashboardInfo, @@ -72,11 +73,13 @@ export async function syncFromBillmanager( const fetchVpsPayments = !skipVpsPayments const fetchTariffs = !skipTariffs + const fallbackCurrency = syncFallbackCurrency(account) + const [vdsItems, paymentItems, dashboardInfo, tariffResult] = await Promise.all([ fetchVpsPayments ? fetchVds(apiBaseUrl, authinfo) : [], fetchVpsPayments ? fetchPayments(apiBaseUrl, authinfo, {}) : [], fetchVpsPayments - ? fetchDashboardInfo(apiBaseUrl, authinfo, { fallbackCurrency: account.currency }).catch(() => null) + ? fetchDashboardInfo(apiBaseUrl, authinfo, { fallbackCurrency }).catch(() => null) : null, fetchTariffs ? fetchVdsOrderPricelistAllDatacenters(apiBaseUrl, authinfo).catch((err) => { @@ -253,7 +256,7 @@ export async function syncFromBillmanager( db.update(schema.providerAccounts) .set({ balanceApi: dashboardInfo.balance, - balanceCurrency: dashboardInfo.currency || 'RUB', + balanceCurrency: dashboardInfo.currency || fallbackCurrency, balanceUpdatedAt: new Date().toISOString(), enoughmoneyto: dashboardInfo.enoughmoneyto || '', }) diff --git a/apps/api/src/services/fourvps/context.ts b/apps/api/src/services/fourvps/context.ts index f41842a..9119e17 100644 --- a/apps/api/src/services/fourvps/context.ts +++ b/apps/api/src/services/fourvps/context.ts @@ -9,6 +9,7 @@ export interface FourvpsSyncAccount extends AccountRow { apiBaseUrl: string panelId: number | null apiKey: string + providerBaseCurrency?: string | null } export function resolveFourvpsApi( @@ -29,5 +30,5 @@ export function fourvpsAccountRowForSync( const cred = String(accountRow.apiCredentials || '').trim() const { panelId, apiKey } = parseFourVpsCredentials(cred) if (apiType !== '4vps' || !apiBaseUrl || !apiKey) return null - return { ...accountRow, apiType: '4vps', apiBaseUrl, panelId, apiKey } + return { ...accountRow, apiType: '4vps', apiBaseUrl, panelId, apiKey, providerBaseCurrency: providerRow?.baseCurrency ?? null } } diff --git a/apps/api/src/services/fourvps/sync.ts b/apps/api/src/services/fourvps/sync.ts index ae70a08..a4c1545 100644 --- a/apps/api/src/services/fourvps/sync.ts +++ b/apps/api/src/services/fourvps/sync.ts @@ -6,6 +6,7 @@ import { and, eq, like, or } from 'drizzle-orm' import { getDb, schema } from '@cfdm/db' import type { FourvpsSyncAccount } from './context.js' +import { syncFallbackCurrency } from '@cfdm/shared/utils/account-balance' import { mapServerToVps } from './mappers.js' import { fetchDcList, @@ -77,10 +78,12 @@ export async function syncFromFourvps( const fetchVpsData = !skipVpsPayments const fetchTariffs = !skipTariffs + const fallbackCurrency = syncFallbackCurrency(account) + const [servers, balanceInfo, tariffItems, dcMap] = await Promise.all([ fetchVpsData ? fetchMyServers(apiBaseUrl, credentials) : [], fetchVpsData - ? fetchUserBalance(apiBaseUrl, credentials, account.currency || 'RUB').catch(() => null) + ? fetchUserBalance(apiBaseUrl, credentials, fallbackCurrency).catch(() => null) : null, fetchTariffs ? fetchTarifList(apiBaseUrl, credentials).catch(() => []) : [], fetchVpsData || fetchTariffs ? fetchDcList(apiBaseUrl, credentials).catch(() => new Map()) : new Map(), @@ -217,7 +220,7 @@ export async function syncFromFourvps( db.update(schema.providerAccounts) .set({ balanceApi: balanceInfo.balance, - balanceCurrency: balanceInfo.currency || 'RUB', + balanceCurrency: balanceInfo.currency || fallbackCurrency, balanceUpdatedAt: new Date().toISOString(), enoughmoneyto: '', }) diff --git a/apps/api/src/services/providers/billmanager-adapter.ts b/apps/api/src/services/providers/billmanager-adapter.ts index 850c763..7ed8347 100644 --- a/apps/api/src/services/providers/billmanager-adapter.ts +++ b/apps/api/src/services/providers/billmanager-adapter.ts @@ -1,6 +1,7 @@ import { syncFromBillmanager } from '../billmanager/sync.js' import { testConnection as bmTestConnection, fetchDashboardInfo } from '../billmanager/operations.js' import type { BillmanagerSyncAccount } from '../billmanager/context.js' +import { syncFallbackCurrency } from '@cfdm/shared/utils/account-balance' import type { ProviderAdapter, SyncResult } from './types.js' @@ -28,12 +29,13 @@ export const billmanagerAdapter: ProviderAdapter = { }, async fetchBalance(account: BillmanagerSyncAccount) { + const fallbackCurrency = syncFallbackCurrency(account) const info = await fetchDashboardInfo(account.apiBaseUrl, String(account.apiCredentials).trim(), { - fallbackCurrency: account.currency, + fallbackCurrency, }) return { balance: info.balance, - currency: info.currency || 'RUB', + currency: info.currency || fallbackCurrency, enoughmoneyto: info.enoughmoneyto || '', } }, diff --git a/apps/api/src/services/providers/fourvps-adapter.ts b/apps/api/src/services/providers/fourvps-adapter.ts index c69e828..43bfaa9 100644 --- a/apps/api/src/services/providers/fourvps-adapter.ts +++ b/apps/api/src/services/providers/fourvps-adapter.ts @@ -4,6 +4,7 @@ import { testConnection as fourvpsTestConnection, } from '../fourvps/operations.js' import type { FourvpsSyncAccount } from '../fourvps/context.js' +import { syncFallbackCurrency } from '@cfdm/shared/utils/account-balance' import type { ProviderAdapter, SyncResult } from './types.js' @@ -33,10 +34,11 @@ export const fourvpsAdapter: ProviderAdapter = { async fetchBalance(account: FourvpsSyncAccount) { const cred = account.panelId != null ? `${account.panelId}:${account.apiKey}` : account.apiKey - const info = await fetchFourvpsBalance(account.apiBaseUrl, cred, account.currency || 'RUB') + const fallbackCurrency = syncFallbackCurrency(account) + const info = await fetchFourvpsBalance(account.apiBaseUrl, cred, fallbackCurrency) return { balance: info.balance, - currency: info.currency || 'RUB', + currency: info.currency || fallbackCurrency, enoughmoneyto: '', } }, diff --git a/apps/api/src/services/providers/userapi-adapter.ts b/apps/api/src/services/providers/userapi-adapter.ts index 8829a22..20eea56 100644 --- a/apps/api/src/services/providers/userapi-adapter.ts +++ b/apps/api/src/services/providers/userapi-adapter.ts @@ -1,6 +1,7 @@ import { syncFromUserApi } from '../userapi/sync.js' import { fetchBalance, testConnection } from '../userapi/operations.js' import type { UserApiSyncAccount } from '../userapi/context.js' +import { syncFallbackCurrency } from '@cfdm/shared/utils/account-balance' import type { ProviderAdapter, SyncResult } from './types.js' @@ -28,14 +29,15 @@ export const userapiAdapter: ProviderAdapter = { }, async fetchBalance(account: UserApiSyncAccount) { + const fallbackCurrency = syncFallbackCurrency(account) const info = await fetchBalance( account.apiBaseUrl, account.apiToken, - account.currency || 'RUB', + fallbackCurrency, ) return { balance: info.balance, - currency: info.currency || 'RUB', + currency: info.currency || fallbackCurrency, enoughmoneyto: info.enoughmoneyto || '', } }, diff --git a/apps/api/src/services/userapi/context.ts b/apps/api/src/services/userapi/context.ts index 1b36048..62fd1a7 100644 --- a/apps/api/src/services/userapi/context.ts +++ b/apps/api/src/services/userapi/context.ts @@ -9,6 +9,7 @@ export interface UserApiSyncAccount extends AccountRow { apiType: UserApiType apiBaseUrl: string apiToken: string + providerBaseCurrency?: string | null } export function resolveUserApi( @@ -31,5 +32,11 @@ export function userApiAccountRowForSync( const resolved = resolveUserApi(accountRow, providerRow) const apiToken = parseUserApiToken(accountRow.apiCredentials) if (!resolved || !resolved.apiBaseUrl || !apiToken) return null - return { ...accountRow, apiType: resolved.apiType, apiBaseUrl: resolved.apiBaseUrl, apiToken } + return { + ...accountRow, + apiType: resolved.apiType, + apiBaseUrl: resolved.apiBaseUrl, + apiToken, + providerBaseCurrency: providerRow?.baseCurrency ?? null, + } } diff --git a/apps/api/src/services/userapi/sync.ts b/apps/api/src/services/userapi/sync.ts index a4cf96d..d13a07c 100644 --- a/apps/api/src/services/userapi/sync.ts +++ b/apps/api/src/services/userapi/sync.ts @@ -6,6 +6,7 @@ import { and, eq, like, or } from 'drizzle-orm' import { getDb, schema } from '@cfdm/db' import type { UserApiSyncAccount } from './context.js' +import { syncFallbackCurrency } from '@cfdm/shared/utils/account-balance' import { mapOperationToPayment, mapServerToVps } from './mappers.js' import { fetchBalance, @@ -72,10 +73,12 @@ export async function syncFromUserApi( const fetchVpsData = !skipVpsPayments const fetchTariffs = !skipTariffs + const fallbackCurrency = syncFallbackCurrency(account) + const [servers, balanceInfo, tariffItems, operations] = await Promise.all([ fetchVpsData ? fetchServersWithDetails(apiBaseUrl, credentials) : [], fetchVpsData - ? fetchBalance(apiBaseUrl, credentials, account.currency || 'RUB').catch(() => null) + ? fetchBalance(apiBaseUrl, credentials, fallbackCurrency).catch(() => null) : null, fetchTariffs ? fetchTariffList(apiBaseUrl, credentials).catch(() => []) : [], fetchVpsData ? fetchOperations(apiBaseUrl, credentials).catch(() => []) : [], @@ -223,7 +226,7 @@ export async function syncFromUserApi( ) for (const item of operations) { - const payment = mapOperationToPayment(item, apiType, accountId, account.currency || 'RUB') + const payment = mapOperationToPayment(item, apiType, accountId, fallbackCurrency) if (!payment) continue const note = payment.note if (existingPayments.has(note)) continue @@ -250,7 +253,7 @@ export async function syncFromUserApi( db.update(schema.providerAccounts) .set({ balanceApi: balanceInfo.balance, - balanceCurrency: balanceInfo.currency || 'RUB', + balanceCurrency: balanceInfo.currency || fallbackCurrency, balanceUpdatedAt: new Date().toISOString(), enoughmoneyto: balanceInfo.enoughmoneyto || '', }) diff --git a/apps/web/src/lib/account.ts b/apps/web/src/lib/account.ts index 6ce9371..6829dc6 100644 --- a/apps/web/src/lib/account.ts +++ b/apps/web/src/lib/account.ts @@ -1 +1 @@ -export { accountBalanceApi, accountBalanceCurrency } from '@cfdm/shared/utils/account-balance' +export { accountBalanceApi, accountBalanceCurrency, effectiveAccountBalanceCurrency } from '@cfdm/shared/utils/account-balance' diff --git a/apps/web/src/routes/_auth/accounts.tsx b/apps/web/src/routes/_auth/accounts.tsx index 8d96ff6..5cdaade 100644 --- a/apps/web/src/routes/_auth/accounts.tsx +++ b/apps/web/src/routes/_auth/accounts.tsx @@ -33,7 +33,7 @@ import { providerAccountFormDefaults, } from '@/components/domain/account-edit-sheet' import type { ProviderAccountFormValues } from '@/lib/schemas' -import { accountBalanceApi, accountBalanceCurrency } from '@/lib/account' +import { accountBalanceApi, effectiveAccountBalanceCurrency } from '@/lib/account' import type { ProviderAccount } from '@/types/entities' import { providerByIdMap, accountBillmanagerUiReady, billmanagerSyncableAccounts } from '@/lib/billmanager' import { billingModeLabel, formatCurrency, formatRelativeTime } from '@/lib/format' @@ -337,7 +337,7 @@ function AccountsPage() { cell: (a) => { const provider = providerById.get(a.providerId) if (!accountBillmanagerUiReady(a, provider)) return - const cur = accountBalanceCurrency(a) + const cur = effectiveAccountBalanceCurrency(a, provider) const ext = a as ProviderAccount & { enoughmoneyto?: string } return dataGridCellStack( formatCurrency(accountBalanceApi(a) ?? 0, cur), diff --git a/packages/shared/src/utils/account-balance.ts b/packages/shared/src/utils/account-balance.ts index fda5351..6d14e33 100644 --- a/packages/shared/src/utils/account-balance.ts +++ b/packages/shared/src/utils/account-balance.ts @@ -1,3 +1,5 @@ +import { resolveProviderCurrency } from './currency.js' + /** Баланс API аккаунта (camelCase из API и snake_case в типах). */ export function accountBalanceApi(account: { balance_api?: number | null @@ -16,3 +18,26 @@ export function accountBalanceCurrency(account: { }): string { return account.balance_currency ?? account.balanceCurrency ?? account.currency ?? 'RUB' } + +/** Валюта баланса с учётом baseCurrency хостера (как effectiveVpsTariffCurrency для тарифов). */ +export function effectiveAccountBalanceCurrency( + account: { + balance_currency?: string + balanceCurrency?: string + currency?: string + }, + provider?: { baseCurrency?: string | null } | null, +): string { + const provRaw = (provider?.baseCurrency ?? '').trim() + if (provRaw) return provRaw + return accountBalanceCurrency(account) +} + +export function syncFallbackCurrency( + account: { currency?: string | null; providerBaseCurrency?: string | null }, +): string { + return resolveProviderCurrency( + { baseCurrency: account.providerBaseCurrency }, + account.currency, + ) +} diff --git a/packages/shared/src/utils/currency.ts b/packages/shared/src/utils/currency.ts new file mode 100644 index 0000000..4811b91 --- /dev/null +++ b/packages/shared/src/utils/currency.ts @@ -0,0 +1,12 @@ +/** Валюта хостера: настройки провайдера → аккаунт → fallback. */ +export function resolveProviderCurrency( + provider?: { baseCurrency?: string | null } | null, + accountCurrency?: string | null, + fallback = 'RUB', +): string { + const fromProvider = (provider?.baseCurrency ?? '').trim() + if (fromProvider) return fromProvider + const fromAccount = (accountCurrency ?? '').trim() + if (fromAccount) return fromAccount + return fallback +}