fix(web): корректная валюта VPS в отчётах по месячным расходам
Docker / build (push) Failing after 23s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-06-29 18:08:27 +07:00
co-authored by Cursor
parent 5998887148
commit 46267669ce
3 changed files with 32 additions and 17 deletions
+2 -7
View File
@@ -18,7 +18,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@cfdm
import type { ReactNode } from 'react'
import type { Vps, Provider, Payment, Settings, RatesData } from '@/types/entities'
import { convertCurrency, formatCurrency, monthKey, toIsoCurrency } from '@/lib/format'
import { convertCurrency, convertVpsMonthlyBurnToBase, formatCurrency, monthKey, toIsoCurrency } from '@/lib/format'
import { providerByIdMap } from '@/lib/billmanager'
import { EmptyState } from '@/components/empty-state'
@@ -51,13 +51,8 @@ export function MonthlyExpenseChart({
const monthlyByAccount = new Map<string, number>()
for (const v of vps) {
if (v.status !== 'active') continue
const provider = providerById.get(v.providerId)
const monthly = Number(v.monthlyRate || 0)
const daily = Number(v.dailyRate || 0)
const burn = v.tariffType === 'daily' ? daily * 30 : monthly
const fromCurrency = toIsoCurrency(provider?.baseCurrency || v.currency || baseCurrency)
const converted = convertCurrency(burn, fromCurrency, baseCurrency, ratesData)
const converted = convertVpsMonthlyBurnToBase(v, provider, settings, ratesData)
monthlyByAccount.set(v.providerAccountId, (monthlyByAccount.get(v.providerAccountId) ?? 0) + converted)
}
+19
View File
@@ -314,6 +314,25 @@ export function formatInProviderCurrency(
return formatCurrency(converted.value, converted.currency)
}
/** Месячный burn-rate VPS, сконвертированный в базовую валюту приложения. */
export function convertVpsMonthlyBurnToBase(
vps: Pick<Vps, 'status' | 'tariffType' | 'dailyRate' | 'monthlyRate' | 'currency' | 'providerId'>,
provider: Provider | null | undefined,
appSettings: Settings[] | Settings | null | undefined,
ratesData: RatesData | null,
): number {
if (vps.status !== 'active') return 0
const settings: Partial<Settings> = Array.isArray(appSettings)
? (appSettings[0] ?? {})
: (appSettings ?? {})
const autoConvert = settings.autoConvert !== false
const burn = vpsTariffMonthlyBurn(vps)
if (!Number.isFinite(burn) || burn <= 0) return 0
if (!autoConvert) return burn
const fromCurrency = effectiveVpsTariffCurrency(vps as Vps, provider)
return convertWithProviderRate(burn, fromCurrency, provider, appSettings ?? null, ratesData).value
}
export function monthKey(dateString: string): string {
const date = new Date(dateString)
if (Number.isNaN(date.getTime())) return ''
+11 -10
View File
@@ -9,7 +9,8 @@ import { SectionCards } from '@/components/section-cards'
import { ChartsGrid, MonthlyExpenseChart, PaymentsPieChart, MonthlyTrendChart } from '@/components/domain/charts'
import { exportVpsCsv } from '@/lib/export-csv'
import { normalizeRatesPayload, formatCurrency } from '@/lib/format'
import { convertVpsMonthlyBurnToBase, formatCurrency, normalizeRatesPayload } from '@/lib/format'
import { providerByIdMap } from '@/lib/billmanager'
export const Route = createFileRoute('/_auth/reports')({
loader: ({ context: { queryClient } }) =>
@@ -63,22 +64,22 @@ function ReportsPage() {
}
>
{(snap) => {
const monthly = snap.vps
.filter((v) => v.status === 'active')
.reduce((acc, v) => {
const burn =
v.tariffType === 'daily' ? Number(v.dailyRate || 0) * 30 : Number(v.monthlyRate || 0)
return acc + burn
}, 0)
const providerById = providerByIdMap(snap.providers)
const baseCurrency = (snap.settings[0]?.baseCurrency ?? 'RUB').toUpperCase()
const monthly = snap.vps.reduce(
(acc, v) =>
acc + convertVpsMonthlyBurnToBase(v, providerById.get(v.providerId), snap.settings, ratesData),
0,
)
return (
<>
<SectionCards
items={[
{
label: 'Расход/мес',
value: formatCurrency(monthly, snap.vps[0]?.currency ?? 'RUB'),
value: formatCurrency(monthly, baseCurrency),
icon: <TrendingUpIcon className="size-4" />,
hint: 'в валюте VPS',
hint: `в ${baseCurrency}`,
},
{
label: 'Платежей',