fix(dashboard): не копировать оценку расходов на все месяцы
Docker / build (push) Failing after 20s

Оценка burn только за текущий месяц; KPI совпадает с convertVpsMonthlyBurnToBase.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-07-20 01:00:11 +07:00
co-authored by Cursor
parent d25beec289
commit 8a4934940f
4 changed files with 65 additions and 18 deletions
+3 -1
View File
@@ -361,7 +361,9 @@ export function DashboardExpensesChart({
const hasData = data.some((row) => row.amount > 0)
const description =
mode === 'estimate' ? 'Оценка по активным VPS за текущий год' : 'Последние 12 мес'
mode === 'estimate'
? 'Нет списаний в учёте — оценка тарифов за текущий месяц'
: 'Списания и платежи за VPS по месяцам'
return (
<Card className={className}>
+37 -3
View File
@@ -52,7 +52,7 @@ describe('chart-analytics', () => {
expect(expenseOnly[2]?.amount).toBe(0)
})
it('falls back to VPS burn estimate when no expense records exist', () => {
it('falls back to VPS burn estimate for current month only when no expense records', () => {
const payments: Payment[] = [
{
id: 'p1',
@@ -71,12 +71,46 @@ describe('chart-analytics', () => {
[],
[activeVps],
[],
2026,
new Date().getFullYear(),
settings,
null,
)
expect(mode).toBe('estimate')
expect(rows[2]?.amount).toBe(1500)
const currentMonth = new Date().getMonth()
expect(rows[currentMonth]?.amount).toBe(1500)
for (let i = 0; i < 12; i++) {
if (i === currentMonth) continue
expect(rows[i]?.amount).toBe(0)
}
})
it('uses actual expense payments when present', () => {
const payments: Payment[] = [
{
id: 'p1',
type: 'direct_vps_payment',
date: '2026-02-10',
amount: 2000,
currency: 'RUB',
providerAccountId: 'a1',
vpsId: 'v1',
note: '',
},
]
const { rows, mode } = aggregateDashboardExpensesByMonthYear(
payments,
[],
[activeVps],
[],
2026,
settings,
null,
)
expect(mode).toBe('actual')
expect(rows[1]?.amount).toBe(2000)
expect(rows[2]?.amount).toBe(0)
})
})
+10 -7
View File
@@ -168,6 +168,14 @@ export function aggregateEstimatedVpsBurnByMonthYear(
ratesData: RatesData | null,
): { month: string; amount: number }[] {
const providerById = providerByIdMap(providers)
const now = new Date()
const byMonth = Array.from({ length: 12 }, () => 0)
// Без факта списаний не выдумываем историю: оценка = текущий burn только в текущем месяце.
if (year !== now.getFullYear()) {
return bucketsToRows(byMonth)
}
const monthlyBurn = vps
.filter((item) => item.status === 'active')
.reduce(
@@ -177,13 +185,8 @@ export function aggregateEstimatedVpsBurnByMonthYear(
0,
)
const now = new Date()
const rounded = Math.round(monthlyBurn)
return Array.from({ length: 12 }, (_, index) => ({
month: formatMonthShortRu(index),
amount: year === now.getFullYear() && index <= now.getMonth() ? rounded : 0,
}))
byMonth[now.getMonth()] = monthlyBurn
return bucketsToRows(byMonth)
}
export function aggregateDashboardExpensesByMonthYear(
+15 -7
View File
@@ -35,7 +35,8 @@ import { cn } from '@cfdm/ui/lib/utils'
import { computeInventoryHealth } from '@/lib/inventory-health'
import { buildAtRiskAccounts, type AtRiskAccount } from '@/lib/account-health'
import { formatInBaseCurrency, normalizeRatesPayload, vpsStatusLabel } from '@/lib/format'
import { formatInBaseCurrency, normalizeRatesPayload, vpsStatusLabel, convertVpsMonthlyBurnToBase, formatCurrency } from '@/lib/format'
import { providerByIdMap } from '@/lib/billmanager'
import { exportActiveVpsCsv } from '@/lib/export-csv'
import {
ChartsGrid,
@@ -246,6 +247,18 @@ function DashboardPage() {
const totalCount = stats?.totalVpsCount ?? snap.vps.length
const expiringCount = stats?.expiringWithin7Days ?? 0
const issuesCount = issues.length
const providerById = providerByIdMap(snap.providers)
const monthlyBurnBase = activeVps.reduce(
(sum, item) =>
sum +
convertVpsMonthlyBurnToBase(
item,
providerById.get(item.providerId),
snap.settings,
ratesData,
),
0,
)
const handleGoToIssues = () => {
setActiveTab('issues')
@@ -272,12 +285,7 @@ function DashboardPage() {
{
id: 'burn',
label: 'Расход в месяц',
value: formatInBaseCurrency(
stats?.monthlyBurnEstimate ?? 0,
baseCur,
snap.settings,
ratesData,
),
value: formatCurrency(monthlyBurnBase, baseCur),
icon: <TrendingUpIcon className="size-4" />,
iconClassName: 'text-info',
to: '/reports',