fix(accounts): учитывать baseCurrency хостера при отображении и синке баланса
Docker / build (push) Has been cancelled
Docker / build (push) Has been cancelled
API не всегда возвращает валюту (VDSina UserAPI); раньше подставлялся RUB вместо USD из настроек провайдера. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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 || '',
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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: '',
|
||||
})
|
||||
|
||||
@@ -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 || '',
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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: '',
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 || '',
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 || '',
|
||||
})
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { accountBalanceApi, accountBalanceCurrency } from '@cfdm/shared/utils/account-balance'
|
||||
export { accountBalanceApi, accountBalanceCurrency, effectiveAccountBalanceCurrency } from '@cfdm/shared/utils/account-balance'
|
||||
|
||||
@@ -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 <span className="text-muted-foreground">—</span>
|
||||
const cur = accountBalanceCurrency(a)
|
||||
const cur = effectiveAccountBalanceCurrency(a, provider)
|
||||
const ext = a as ProviderAccount & { enoughmoneyto?: string }
|
||||
return dataGridCellStack(
|
||||
formatCurrency(accountBalanceApi(a) ?? 0, cur),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user