diff --git a/src/lib/utils.js b/src/lib/utils.js index 5077cfe..786fba5 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -174,22 +174,15 @@ function toIsoCurrency(currency) { } /** - * Валюта суммы тарифа VPS: поле vps.currency, иначе (только у хостеров без API) — валюта приёма из справочника. - * Для BILLmanager нельзя подставлять baseCurrency хостера при пустом поле: тариф в валюте из API (часто RUB). - * У ручных хостеров «USD» в VPS часто дефолт формы — тогда берём валюту хостера. + * Валюта тарифа VPS = валюта хостера (provider.baseCurrency). + * vps.currency — только как запасной вариант, если хостер не найден. */ export function effectiveVpsTariffCurrency(vps, provider) { - const ownRaw = (vps?.currency || '').trim() - const ownIso = ownRaw ? toIsoCurrency(ownRaw) : null const provRaw = (provider?.baseCurrency || '').trim() - const provIso = provRaw ? toIsoCurrency(provRaw) : null - const noApi = !(provider?.apiType || '').trim() - if (noApi && ownIso === 'USD' && provIso && provIso !== 'USD') { - return provIso - } - if (ownIso) return ownIso - if (provIso && noApi) return provIso - return noApi ? 'USD' : 'RUB' + if (provRaw) return toIsoCurrency(provRaw) + const ownRaw = (vps?.currency || '').trim() + if (ownRaw) return toIsoCurrency(ownRaw) + return 'RUB' } /** diff --git a/src/pages/DashboardPage.jsx b/src/pages/DashboardPage.jsx index 1e30846..54a0fb2 100644 --- a/src/pages/DashboardPage.jsx +++ b/src/pages/DashboardPage.jsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react' import { Link } from 'react-router-dom' -import { convertCurrency, formatCurrency, monthKey } from '../lib/utils' +import { convertCurrency, effectiveVpsTariffCurrency, formatCurrency, monthKey } from '../lib/utils' import { getPaidUntilDate as computePaidUntil } from '../lib/paid-until' import { computeInventoryHealth, formatSyncSummaryLine } from '../lib/inventory-health' import { fetchSyncStatus } from '../lib/api' @@ -37,6 +37,11 @@ export function DashboardPage({ db = {}, settings, ratesData }) { const activeVpsCount = vps.filter((item) => item.status === 'active').length + const providerById = useMemo( + () => new Map(providers.map((p) => [p.id, p])), + [providers], + ) + const monthForecast = useMemo(() => { return vps .filter((item) => item.status === 'active') @@ -46,9 +51,10 @@ export function DashboardPage({ db = {}, settings, ratesData }) { tariffType === 'daily' ? Number(item.dailyRate || 0) * 30 : Number(item.monthlyRate || 0) - return acc + convertCurrency(amount, item.currency || 'USD', baseCurrency, ratesData) + const cur = effectiveVpsTariffCurrency(item, providerById.get(item.providerId)) + return acc + convertCurrency(amount, cur, baseCurrency, ratesData) }, 0) - }, [vps, baseCurrency, ratesData]) + }, [vps, baseCurrency, ratesData, providerById]) const monthExpenses = useMemo(() => { return [...payments, ...balanceLedger] @@ -144,14 +150,15 @@ export function DashboardPage({ db = {}, settings, ratesData }) { tariffType === 'daily' ? Number(item.dailyRate || 0) * 30 : Number(item.monthlyRate || 0) - map[key].forecast += convertCurrency(amount, item.currency || 'USD', baseCurrency, ratesData) + const cur = effectiveVpsTariffCurrency(item, providerById.get(item.providerId)) + map[key].forecast += convertCurrency(amount, cur, baseCurrency, ratesData) } return Object.values(map).sort((a, b) => { if (a.key === '__none__') return 1 if (b.key === '__none__') return -1 return a.label.localeCompare(b.label, 'ru') }) - }, [vps, baseCurrency, ratesData]) + }, [vps, baseCurrency, ratesData, providerById]) const forecastByAccount = useMemo(() => { return providerAccounts @@ -164,13 +171,14 @@ export function DashboardPage({ db = {}, settings, ratesData }) { tariffType === 'daily' ? Number(item.dailyRate || 0) * 30 : Number(item.monthlyRate || 0) - forecast += convertCurrency(amount, item.currency || 'USD', baseCurrency, ratesData) + const cur = effectiveVpsTariffCurrency(item, providerById.get(item.providerId)) + forecast += convertCurrency(amount, cur, baseCurrency, ratesData) } return { account: acc, count: items.length, forecast } }) .filter((row) => row.count > 0) .sort((a, b) => b.forecast - a.forecast) - }, [vps, providerAccounts, baseCurrency, ratesData]) + }, [vps, providerAccounts, baseCurrency, ratesData, providerById]) const accountBalances = providerAccounts.map((account) => { if (account.balance_api != null && Number.isFinite(Number(account.balance_api))) { diff --git a/src/pages/ReportsPage.jsx b/src/pages/ReportsPage.jsx index bf2097b..0f6e75c 100644 --- a/src/pages/ReportsPage.jsx +++ b/src/pages/ReportsPage.jsx @@ -2,6 +2,7 @@ import { useMemo, useState } from 'react' import { convertCurrency, downloadTextFile, + effectiveVpsTariffCurrency, formatCurrency, toCsv, vpsStatusLabel, @@ -134,7 +135,7 @@ export function ReportsPage({ db, settings, ratesData }) { city: vps.city || '', status: vps.status, expense: Number(total.toFixed(2)), - currency: vps.currency || 'USD', + currency: effectiveVpsTariffCurrency(vps, provider), } }) }, [db.payments, db.balanceLedger, db.providers, db.providerAccounts, db.vps, filters]) diff --git a/src/pages/ResourcesPage.jsx b/src/pages/ResourcesPage.jsx index 9fa4b36..09f003a 100644 --- a/src/pages/ResourcesPage.jsx +++ b/src/pages/ResourcesPage.jsx @@ -1,20 +1,22 @@ import { useMemo, useState } from 'react' import { convertCurrency, + effectiveVpsTariffCurrency, formatCurrency, } from '../lib/utils' import { EmptyState } from '../components/EmptyState' import { PageHeader } from '../components/PageHeader' import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps' -function vpsMonthlyEstimateInBase(item, baseCurrency, ratesData) { +function vpsMonthlyEstimateInBase(item, baseCurrency, ratesData, providerById) { if (item.status !== 'active') return 0 const tariffType = item.tariffType || (Number(item.dailyRate || 0) > 0 ? 'daily' : 'monthly') const amount = tariffType === 'daily' ? Number(item.dailyRate || 0) * 30 : Number(item.monthlyRate || 0) - return convertCurrency(amount, item.currency || 'USD', baseCurrency, ratesData) + const cur = effectiveVpsTariffCurrency(item, providerById.get(item.providerId)) + return convertCurrency(amount, cur, baseCurrency, ratesData) } export function ResourcesPage({ db, settings, ratesData }) { @@ -74,6 +76,11 @@ export function ResourcesPage({ db, settings, ratesData }) { }) }, [db.vps, filters, customFields]) + const providerById = useMemo( + () => new Map((db.providers || []).map((p) => [p.id, p])), + [db.providers], + ) + const groups = useMemo(() => { const map = new Map() const accounts = db.providerAccounts || [] @@ -123,7 +130,7 @@ export function ResourcesPage({ db, settings, ratesData }) { g.vcpu += Number(item.vcpu || 0) g.ramGb += Number(item.ramGb || 0) g.diskGb += Number(item.diskGb || 0) - g.forecast += vpsMonthlyEstimateInBase(item, baseCurrency, ratesData) + g.forecast += vpsMonthlyEstimateInBase(item, baseCurrency, ratesData, providerById) } const list = [...map.values()] list.sort((a, b) => { @@ -134,7 +141,7 @@ export function ResourcesPage({ db, settings, ratesData }) { return b.forecast - a.forecast || b.vcpu - a.vcpu }) return list - }, [vpsFiltered, groupBy, db.providerAccounts, baseCurrency, ratesData]) + }, [vpsFiltered, groupBy, db.providerAccounts, baseCurrency, ratesData, providerById]) const maxForecast = useMemo( () => groups.reduce((m, g) => Math.max(m, g.forecast), 0), diff --git a/src/pages/VpsPage.jsx b/src/pages/VpsPage.jsx index 99a82e0..8ec339e 100644 --- a/src/pages/VpsPage.jsx +++ b/src/pages/VpsPage.jsx @@ -374,14 +374,10 @@ export function VpsPage({ db, actions, settings, ratesData }) { customFields.forEach((f) => { customFieldValues[f.key] = form[f.key] ?? '' }) - const prov = providerById.get(form.providerId) - const resolvedCurrency = - (form.currency || '').trim() || (prov?.baseCurrency || '').trim() || 'USD' const payload = { ...restForm, ...customFieldValues, additionalIps, - currency: resolvedCurrency, dailyRate: form.tariffType === 'daily' ? form.dailyRate : '', monthlyRate: form.tariffType === 'monthly' ? form.monthlyRate : '', } @@ -426,7 +422,7 @@ export function VpsPage({ db, actions, settings, ratesData }) { status: vps.status || 'active', tariffType: vps.tariffType || (Number(vps.dailyRate || 0) > 0 ? 'daily' : 'monthly'), - currency: effectiveVpsTariffCurrency(vps, providerById.get(vps.providerId)), + currency: vps.currency || '', dailyRate: vps.dailyRate || '', monthlyRate: vps.monthlyRate || '', createdAt: vps.createdAt || '', @@ -1440,18 +1436,13 @@ export function VpsPage({ db, actions, settings, ratesData }) { {vpsStatusLabel('archived')} -
+
-
+
@@ -1731,21 +1722,13 @@ export function VpsPage({ db, actions, settings, ratesData }) { placeholder="0.00" />
-
- - -
- В списке VPS и конвертации используется эта валюта, а не только валюта хостера в справочнике. + {form.providerId ? ( +
+ + Валюта тарифа: {(providerById.get(form.providerId)?.baseCurrency || '—').toUpperCase()} (из справочника хостера) +
-
+ ) : null}