fix(web): корректная валюта VPS в отчётах по месячным расходам
Docker / build (push) Failing after 23s
Docker / build (push) Failing after 23s
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -18,7 +18,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@cfdm
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
import type { Vps, Provider, Payment, Settings, RatesData } from '@/types/entities'
|
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 { providerByIdMap } from '@/lib/billmanager'
|
||||||
import { EmptyState } from '@/components/empty-state'
|
import { EmptyState } from '@/components/empty-state'
|
||||||
|
|
||||||
@@ -51,13 +51,8 @@ export function MonthlyExpenseChart({
|
|||||||
|
|
||||||
const monthlyByAccount = new Map<string, number>()
|
const monthlyByAccount = new Map<string, number>()
|
||||||
for (const v of vps) {
|
for (const v of vps) {
|
||||||
if (v.status !== 'active') continue
|
|
||||||
const provider = providerById.get(v.providerId)
|
const provider = providerById.get(v.providerId)
|
||||||
const monthly = Number(v.monthlyRate || 0)
|
const converted = convertVpsMonthlyBurnToBase(v, provider, settings, ratesData)
|
||||||
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)
|
|
||||||
monthlyByAccount.set(v.providerAccountId, (monthlyByAccount.get(v.providerAccountId) ?? 0) + converted)
|
monthlyByAccount.set(v.providerAccountId, (monthlyByAccount.get(v.providerAccountId) ?? 0) + converted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -314,6 +314,25 @@ export function formatInProviderCurrency(
|
|||||||
return formatCurrency(converted.value, converted.currency)
|
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 {
|
export function monthKey(dateString: string): string {
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
if (Number.isNaN(date.getTime())) return ''
|
if (Number.isNaN(date.getTime())) return ''
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { SectionCards } from '@/components/section-cards'
|
|||||||
import { ChartsGrid, MonthlyExpenseChart, PaymentsPieChart, MonthlyTrendChart } from '@/components/domain/charts'
|
import { ChartsGrid, MonthlyExpenseChart, PaymentsPieChart, MonthlyTrendChart } from '@/components/domain/charts'
|
||||||
import { exportVpsCsv } from '@/lib/export-csv'
|
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')({
|
export const Route = createFileRoute('/_auth/reports')({
|
||||||
loader: ({ context: { queryClient } }) =>
|
loader: ({ context: { queryClient } }) =>
|
||||||
@@ -63,22 +64,22 @@ function ReportsPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(snap) => {
|
{(snap) => {
|
||||||
const monthly = snap.vps
|
const providerById = providerByIdMap(snap.providers)
|
||||||
.filter((v) => v.status === 'active')
|
const baseCurrency = (snap.settings[0]?.baseCurrency ?? 'RUB').toUpperCase()
|
||||||
.reduce((acc, v) => {
|
const monthly = snap.vps.reduce(
|
||||||
const burn =
|
(acc, v) =>
|
||||||
v.tariffType === 'daily' ? Number(v.dailyRate || 0) * 30 : Number(v.monthlyRate || 0)
|
acc + convertVpsMonthlyBurnToBase(v, providerById.get(v.providerId), snap.settings, ratesData),
|
||||||
return acc + burn
|
0,
|
||||||
}, 0)
|
)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SectionCards
|
<SectionCards
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: 'Расход/мес',
|
label: 'Расход/мес',
|
||||||
value: formatCurrency(monthly, snap.vps[0]?.currency ?? 'RUB'),
|
value: formatCurrency(monthly, baseCurrency),
|
||||||
icon: <TrendingUpIcon className="size-4" />,
|
icon: <TrendingUpIcon className="size-4" />,
|
||||||
hint: 'в валюте VPS',
|
hint: `в ${baseCurrency}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Платежей',
|
label: 'Платежей',
|
||||||
|
|||||||
Reference in New Issue
Block a user