diff --git a/frontend/src/BillingManager.jsx b/frontend/src/BillingManager.jsx index da54da3..ca5d23b 100644 --- a/frontend/src/BillingManager.jsx +++ b/frontend/src/BillingManager.jsx @@ -117,41 +117,6 @@ function BillingManager() { fetchServers(); }, []); - // Пересчёт nextPaymentDate по платежам при загрузке (для уже сохранённых данных) - useEffect(() => { - if (billingData.length === 0 || ratesLoading) return; - setBillingData(prev => { - const updated = prev.map(item => { - const payments = item.payments || []; - if (payments.length === 0) return item; - const last = payments.slice().sort((a, b) => new Date(b.date) - new Date(a.date))[0]; - const computed = (() => { - if (!last?.date) return null; - const amountRUB = (amt, ccy) => { - if (!amt) return 0; - const rub = Number(exchangeRates.RUB) || 1; - if (!ccy || ccy === 'RUB') return amt; - if (ccy === 'USD') return amt * rub; - const c = Number(exchangeRates[ccy]); - return c ? (amt / c) * rub : amt * rub; - }; - const costRUB = amountRUB(item.monthlyCost || 0, item.monthlyCostCurrency || 'USD'); - const amtRUB = amountRUB(last.amount, last.currency || 'USD'); - const months = costRUB > 0 ? Math.max(1, Math.floor(amtRUB / costRUB)) : 1; - const d = new Date(last.date); - d.setMonth(d.getMonth() + months); - return d.toISOString().slice(0, 10); - })(); - const current = new Date(item.nextPaymentDate || 0); - const now = new Date(); - if (computed && current < now) return { ...item, nextPaymentDate: computed }; - return item; - }); - const changed = updated.some((it, i) => it.nextPaymentDate !== prev[i]?.nextPaymentDate); - return changed ? updated : prev; - }); - }, [billingData.length, ratesLoading, exchangeRates]); - // Автоматическая синхронизация: добавляем серверы без записей в биллинге useEffect(() => { if (servers.length === 0 || loading) return; @@ -248,16 +213,40 @@ function BillingManager() { setLoading(true); try { const response = await api.get(`/billing`); - const normalized = (response.data || []).map((item, idx) => ({ - id: item.id ?? String(idx + 1), - payments: Array.isArray(item.payments) + const rates = exchangeRates; // из closure, на первой загрузке может быть {1,1,1} — для USD достаточно + const toRUB = (amt, ccy) => { + if (!amt) return 0; + const rub = Number(rates.RUB) || 1; + if (!ccy || ccy === 'RUB') return amt; + if (ccy === 'USD') return amt * rub; + const c = Number(rates[ccy]); + return c ? (amt / c) * rub : amt * rub; + }; + const normalized = (response.data || []).map((item, idx) => { + const payments = Array.isArray(item.payments) ? item.payments : (item.lastPaymentDate && item.lastPaymentAmount ? [{ date: item.lastPaymentDate, amount: item.lastPaymentAmount, currency: item.lastPaymentCurrency || 'USD', note: 'Импортировано' }] : [] - ), - ...item, - })); + ); + const merged = { id: item.id ?? String(idx + 1), payments, ...item }; + if (payments.length > 0) { + const last = payments.slice().sort((a, b) => new Date(b.date) - new Date(a.date))[0]; + if (last?.date) { + const current = new Date(item.nextPaymentDate || 0); + const now = new Date(); + if (current < now || !item.nextPaymentDate) { + const costRUB = toRUB(item.monthlyCost || 0, item.monthlyCostCurrency || 'USD'); + const amtRUB = toRUB(last.amount, last.currency || 'USD'); + const months = costRUB > 0 ? Math.max(1, Math.floor(amtRUB / costRUB)) : 1; + const d = new Date(last.date); + d.setMonth(d.getMonth() + months); + merged.nextPaymentDate = d.toISOString().slice(0, 10); + } + } + } + return merged; + }); setBillingData(normalized); setError(''); } catch (err) {