"use strict"; (function () { const STORAGE_KEY = "ya_stats_business_finance_v4"; const LEGACY_KEYS = [ "ya_stats_business_finance_v3", "ya_stats_business_finance_v2", "ya_stats_business_finance_v1", ]; const HISTORY_LIMIT = 6; let cachedSubscription = null; let historyCache = null; let historyLoadWarning = ""; const MONTH_NAMES = [ "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь", ]; function core() { return (window.YAStatsCabinet && window.YAStatsCabinet.core) || {}; } function formatMoney(value) { if (typeof core().formatMoney === "function") return core().formatMoney(value); return Number(value || 0).toLocaleString("ru-RU") + " ₽"; } function formatPercent(value) { if (typeof core().formatPercent === "function") return core().formatPercent(value); return Number(value || 0).toLocaleString("ru-RU", { minimumFractionDigits: 0, maximumFractionDigits: 1, }) + "%"; } function escapeHtml(value) { if (typeof core().escapeHtml === "function") return core().escapeHtml(value); return String(value || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function toNumber(value) { const parsed = Number(value || 0); return Number.isFinite(parsed) ? parsed : 0; } function currentMonthKey() { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; } function getProfileTariffCode() { const candidates = [ cachedSubscription, window.__yaBusinessFinanceSubscription, ]; const subscription = candidates.find((item) => item && typeof item === "object") || {}; return String( subscription.feature_tariff_code || subscription.tariff_code || subscription.tariff_name || "" ).trim().toLowerCase(); } function hasBusinessFinanceAccess() { return ["pro", "business"].includes(getProfileTariffCode()); } async function ensureBusinessFinanceSubscription() { try { cachedSubscription = null; const response = await fetch("/auth/site/settings", { credentials: "same-origin", cache: "no-store", }); if (response.ok) { const data = await response.json(); cachedSubscription = data.subscription || null; window.__yaBusinessFinanceSubscription = cachedSubscription; return cachedSubscription; } } catch (error) { return null; } return null; } function getBusinessFinanceUpgradeHtml() { return `

Этот раздел доступен только на тарифах Pro и Business.

Перейдите на более высокий тариф, чтобы сохранять финансы бизнеса по месяцам, сравнивать историю и скачивать PDF-отчеты.
`; } function normalizeMonthKey(value) { if (typeof value === "string" && /^\d{4}-\d{2}$/.test(value)) return value; return currentMonthKey(); } function monthLabel(monthKey) { const normalized = normalizeMonthKey(monthKey); const [year, month] = normalized.split("-"); const index = Math.max(0, Math.min(11, Number(month) - 1)); return `${MONTH_NAMES[index]} ${year}`; } function buildMonthOptions() { const options = []; const cursor = new Date(); cursor.setDate(1); for (let index = 0; index < HISTORY_LIMIT; index += 1) { const key = `${cursor.getFullYear()}-${String(cursor.getMonth() + 1).padStart(2, "0")}`; options.push({ value: key, label: monthLabel(key) }); cursor.setMonth(cursor.getMonth() + 1); } return options; } function normalizeRecord(item) { const month = normalizeMonthKey(item && item.month); const revenue = Math.round(toNumber(item && item.revenue) * 100) / 100; const planRevenue = Math.round(toNumber(item && item.planRevenue) * 100) / 100; const planSpend = Math.round(toNumber(item && item.planSpend) * 100) / 100; const payroll = Math.round(toNumber(item && item.payroll) * 100) / 100; const rent = Math.round(toNumber(item && item.rent) * 100) / 100; const taxes = Math.round(toNumber(item && item.taxes) * 100) / 100; const adSpend = Math.round(toNumber(item && item.adSpend) * 100) / 100; const otherSpend = Math.round(toNumber(item && item.otherSpend) * 100) / 100; const note = String(item && item.note || "").trim(); const totalSpend = Math.round((payroll + rent + taxes + adSpend + otherSpend) * 100) / 100; const profit = Math.round((revenue - totalSpend) * 100) / 100; const roi = totalSpend > 0 ? Math.round(((profit / totalSpend) * 100) * 10) / 10 : null; const adShare = revenue > 0 ? Math.round(((adSpend / revenue) * 100) * 10) / 10 : null; const payrollShare = revenue > 0 ? Math.round(((payroll / revenue) * 100) * 10) / 10 : null; return { month, label: monthLabel(month), revenue, planRevenue, planSpend, payroll, rent, taxes, adSpend, otherSpend, note, totalSpend, profit, roi, adShare, payrollShare, breakEvenRevenue: totalSpend, updatedAt: item && item.updatedAt ? item.updatedAt : new Date().toISOString(), }; } function sortHistory(items) { return items.slice().sort((left, right) => String(right.month).localeCompare(String(left.month))); } function readStorage() { try { const current = window.localStorage.getItem(STORAGE_KEY); if (current) return JSON.parse(current); for (const key of LEGACY_KEYS) { const legacy = window.localStorage.getItem(key); if (legacy) return JSON.parse(legacy); } } catch (error) { return []; } return []; } function loadHistory() { if (!Array.isArray(historyCache)) return []; return sortHistory(historyCache.map(normalizeRecord)).slice(0, HISTORY_LIMIT); } function saveHistory(items) { const next = sortHistory(items.map(normalizeRecord)).slice(0, HISTORY_LIMIT); historyCache = next; return next; } async function fetchHistoryFromServer() { const response = await fetch("/auth/site/business-finance", { credentials: "same-origin", cache: "no-store", }); const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(data.detail || "Не удалось загрузить финансы бизнеса."); } const items = Array.isArray(data.items) ? data.items : []; return saveHistory(items); } async function persistHistory(items) { const next = sortHistory(items.map(normalizeRecord)).slice(0, HISTORY_LIMIT); const response = await fetch("/auth/site/business-finance", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "same-origin", body: JSON.stringify({ items: next }), }); const data = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(data.detail || "Не удалось сохранить финансы бизнеса."); } historyCache = Array.isArray(data.items) ? sortHistory(data.items.map(normalizeRecord)).slice(0, HISTORY_LIMIT) : next; try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(historyCache)); } catch (error) { // local cache is optional; server is the source of truth } return historyCache; } async function ensureHistoryLoaded() { if (Array.isArray(historyCache)) return historyCache; const legacy = readStorage(); historyLoadWarning = ""; try { const serverItems = await fetchHistoryFromServer(); if (serverItems.length) { try { window.localStorage.setItem(STORAGE_KEY, JSON.stringify(serverItems)); } catch (error) { // ignore optional browser cache write errors } return serverItems; } } catch (error) { if (Array.isArray(legacy) && legacy.length) { historyLoadWarning = "Серверное хранилище пока недоступно. Показываю данные из этого браузера."; historyCache = sortHistory(legacy.map(normalizeRecord)).slice(0, HISTORY_LIMIT); return historyCache; } historyLoadWarning = String(error && error.message || "Не удалось загрузить финансы бизнеса."); historyCache = []; return historyCache; } if (Array.isArray(legacy) && legacy.length) { try { return await persistHistory(legacy); } catch (error) { historyLoadWarning = "Форма открыта, но серверное сохранение пока недоступно. Старые данные показаны локально."; historyCache = sortHistory(legacy.map(normalizeRecord)).slice(0, HISTORY_LIMIT); return historyCache; } } historyCache = []; return historyCache; } function getRecord(month) { const key = normalizeMonthKey(month); return loadHistory().find((item) => item.month === key) || normalizeRecord({ month: key }); } async function upsertRecord(payload) { const record = normalizeRecord(payload); const history = loadHistory().filter((item) => item.month !== record.month); history.push(record); return persistHistory(history); } function buildPlanFact(record) { const revenueGap = record.planRevenue > 0 ? Math.round((record.revenue - record.planRevenue) * 100) / 100 : null; const spendGap = record.planSpend > 0 ? Math.round((record.totalSpend - record.planSpend) * 100) / 100 : null; let revenueStatus = "План не задан"; let revenueTone = "text-yellow-400"; if (record.planRevenue > 0) { revenueStatus = revenueGap >= 0 ? "План по выручке выполняется" : "Выручка ниже плана"; revenueTone = revenueGap >= 0 ? "text-green-400" : "text-red-400"; } let spendStatus = "Лимит не задан"; let spendTone = "text-yellow-400"; if (record.planSpend > 0) { spendStatus = spendGap <= 0 ? "Расходы в лимите" : "Расходы выше лимита"; spendTone = spendGap <= 0 ? "text-green-400" : "text-red-400"; } return { revenueGap, spendGap, revenueStatus, revenueTone, spendStatus, spendTone, }; } function buildExpenseNorm(record, previousRecord) { const revenueGrowth = previousRecord && previousRecord.revenue > 0 ? ((record.revenue - previousRecord.revenue) / previousRecord.revenue) * 100 : null; const adGrowth = previousRecord && previousRecord.adSpend > 0 ? ((record.adSpend - previousRecord.adSpend) / previousRecord.adSpend) * 100 : null; const payrollGrowth = previousRecord && previousRecord.payroll > 0 ? ((record.payroll - previousRecord.payroll) / previousRecord.payroll) * 100 : null; const reasons = []; if (record.revenue > 0 && record.totalSpend > record.revenue) { reasons.push("все расходы уже выше выручки"); } if (record.adShare != null && record.adShare > 35) { reasons.push("реклама забирает слишком большую долю выручки"); } if (record.payrollShare != null && record.payrollShare > 45) { reasons.push("зарплаты съедают слишком большую долю выручки"); } if (revenueGrowth != null && adGrowth != null && adGrowth > revenueGrowth + 10) { reasons.push("реклама растет быстрее выручки"); } if (revenueGrowth != null && payrollGrowth != null && payrollGrowth > revenueGrowth + 10) { reasons.push("зарплаты растут быстрее выручки"); } if (record.revenue <= 0 && record.totalSpend > 0) { return { label: "Нужны данные", toneClass: "text-yellow-400", description: "Пока нет выручки, поэтому нельзя честно оценить норму расходов.", }; } if (!reasons.length) { return { label: "В норме", toneClass: "text-green-400", description: "Пока расходы выглядят здорово относительно выручки. Перегрева не видно.", }; } return { label: "Проверить", toneClass: "text-red-400", description: `Сейчас стоит проверить: ${reasons.join(", ")}.`, }; } function buildSummary(record, previousRecord, expenseNorm) { const lines = []; if (record.revenue <= 0 && record.totalSpend > 0) { lines.push("Пока внесены только расходы. Добавьте выручку, чтобы увидеть реальный итог бизнеса."); } else if (record.profit >= 0) { lines.push(`Сейчас бизнес остается в плюсе примерно на ${formatMoney(record.profit)} после всех расходов.`); } else { lines.push(`Сейчас бизнес в минусе примерно на ${formatMoney(Math.abs(record.profit))}. Стоит пересмотреть расходы или поднять выручку.`); } lines.push(`Точка безубыточности на этот месяц: ${formatMoney(record.breakEvenRevenue)}.`); lines.push(`Норма расходов: ${expenseNorm.label}. ${expenseNorm.description}`); if (record.adShare != null) { lines.push(`Реклама занимает около ${formatPercent(record.adShare)} от выручки за месяц.`); } if (record.note) { lines.push(`Комментарий к месяцу: ${record.note}`); } if (previousRecord) { const revenueDiff = record.revenue - previousRecord.revenue; const profitDiff = record.profit - previousRecord.profit; lines.push( revenueDiff >= 0 ? `Выручка выше прошлого месяца на ${formatMoney(revenueDiff)}.` : `Выручка ниже прошлого месяца на ${formatMoney(Math.abs(revenueDiff))}.` ); lines.push( profitDiff >= 0 ? `Финальный результат лучше прошлого месяца на ${formatMoney(profitDiff)}.` : `Финальный результат хуже прошлого месяца на ${formatMoney(Math.abs(profitDiff))}.` ); } else { lines.push("Как только появится второй месяц, раздел начнет показывать сравнение с прошлым периодом."); } return lines; } function buildChart(history) { if (!history.length) { return `
История пока пустая. Сохраните хотя бы один месяц, и здесь появится сравнение выручки, расходов и структуры затрат.
`; } const ordered = history.slice().reverse(); const maxValue = Math.max(1, ...ordered.flatMap((item) => [item.revenue, item.totalSpend, item.planRevenue, item.planSpend])); return `
${ordered.map((item) => { const revenueWidth = Math.max(3, Math.round((item.revenue / maxValue) * 100)); const totalSpendWidth = Math.max(3, Math.round((item.totalSpend / maxValue) * 100)); const payrollWidth = item.totalSpend > 0 ? (item.payroll / item.totalSpend) * 100 : 0; const rentWidth = item.totalSpend > 0 ? (item.rent / item.totalSpend) * 100 : 0; const taxesWidth = item.totalSpend > 0 ? (item.taxes / item.totalSpend) * 100 : 0; const adWidth = item.totalSpend > 0 ? (item.adSpend / item.totalSpend) * 100 : 0; const otherWidth = item.totalSpend > 0 ? (item.otherSpend / item.totalSpend) * 100 : 0; return `

${escapeHtml(item.label)}

${item.profit >= 0 ? "Плюс" : "Минус"} ${escapeHtml(formatMoney(item.profit))}

Выручка ${escapeHtml(formatMoney(item.revenue))}${item.planRevenue > 0 ? ` / план ${escapeHtml(formatMoney(item.planRevenue))}` : ""}
Все расходы ${escapeHtml(formatMoney(item.totalSpend))}${item.planSpend > 0 ? ` / лимит ${escapeHtml(formatMoney(item.planSpend))}` : ""}
Зарплаты ${escapeHtml(formatMoney(item.payroll))} Аренда ${escapeHtml(formatMoney(item.rent))} Налоги ${escapeHtml(formatMoney(item.taxes))} Реклама ${escapeHtml(formatMoney(item.adSpend))} Прочее ${escapeHtml(formatMoney(item.otherSpend))}
`; }).join("")}
`; } function buildHistoryRows(history) { if (!history.length) { return `
Пока нет сохраненных месяцев для сравнения.
`; } return history.map((item) => `

${escapeHtml(item.label)}

Зарплаты ${escapeHtml(formatMoney(item.payroll))} • Аренда ${escapeHtml(formatMoney(item.rent))} • Налоги ${escapeHtml(formatMoney(item.taxes))} • Реклама ${escapeHtml(formatMoney(item.adSpend))} • Прочее ${escapeHtml(formatMoney(item.otherSpend))}

${item.note ? `

${escapeHtml(item.note)}

` : ""}

${escapeHtml(formatMoney(item.profit))}

${item.roi != null ? `ROI ${escapeHtml(formatPercent(item.roi))}` : "ROI пока не считается"}

`).join(""); } function buildPdfHtml(record, history, expenseNorm, planFact) { const generatedAt = new Date().toLocaleString("ru-RU"); const categories = [ { name: "Зарплаты", value: record.payroll, color: "#38bdf8" }, { name: "Аренда", value: record.rent, color: "#8b5cf6" }, { name: "Налоги", value: record.taxes, color: "#22c55e" }, { name: "Реклама", value: record.adSpend, color: "#ffcc00" }, { name: "Прочее", value: record.otherSpend, color: "#fb7185" }, ]; const positiveCategories = categories.filter((item) => item.value > 0); const total = Math.max(record.totalSpend, 1); let cursor = 0; const piePaths = positiveCategories.map((item) => { const ratio = item.value / total; const start = cursor; const end = cursor + ratio; cursor = end; const largeArc = end - start > 0.5 ? 1 : 0; const startAngle = (start * Math.PI * 2) - (Math.PI / 2); const endAngle = (end * Math.PI * 2) - (Math.PI / 2); const x1 = 100 + (84 * Math.cos(startAngle)); const y1 = 100 + (84 * Math.sin(startAngle)); const x2 = 100 + (84 * Math.cos(endAngle)); const y2 = 100 + (84 * Math.sin(endAngle)); const path = `M 100 100 L ${x1.toFixed(3)} ${y1.toFixed(3)} A 84 84 0 ${largeArc} 1 ${x2.toFixed(3)} ${y2.toFixed(3)} Z`; return ``; }).join(""); return ` Финансы бизнеса — ${escapeHtml(record.label)}
YA
YA Stats Web Финансы бизнеса
Отчет сформирован ${escapeHtml(generatedAt)}
${escapeHtml(record.label)} • Красивый фирменный отчет по выручке, расходам, плану и итоговому результату бизнеса.
Выручка${escapeHtml(formatMoney(record.revenue))}
Все расходы${escapeHtml(formatMoney(record.totalSpend))}
Чистый итог${escapeHtml(formatMoney(record.profit))}
ROI${record.roi != null ? escapeHtml(formatPercent(record.roi)) : "—"}

Ключевые выводы

Точка безубыточности
${escapeHtml(formatMoney(record.breakEvenRevenue))}
Минимальная выручка за месяц, чтобы бизнес не ушел в минус.
Норма расходов
${escapeHtml(expenseNorm.label)}
${escapeHtml(expenseNorm.description)}

План-факт месяца

План по выручке
${escapeHtml(formatMoney(record.planRevenue || 0))}
${escapeHtml(planFact.revenueStatus)}
Лимит расходов
${escapeHtml(formatMoney(record.planSpend || 0))}
${escapeHtml(planFact.spendStatus)}

Диаграмма расходов

Расходы ${escapeHtml(formatMoney(record.totalSpend))}
${categories.map((item) => { const share = record.totalSpend > 0 ? (item.value / record.totalSpend) * 100 : 0; return `
${escapeHtml(item.name)}
${escapeHtml(formatPercent(share))} от всех расходов
${escapeHtml(formatMoney(item.value))}
`; }).join("")}

Комментарий месяца

${escapeHtml(record.note || "Комментарий к месяцу пока не заполнен.")}

История за 6 месяцев

${history.map((item) => `
${escapeHtml(item.label)}
Выручка ${escapeHtml(formatMoney(item.revenue))} • Расходы ${escapeHtml(formatMoney(item.totalSpend))} • Итог ${escapeHtml(formatMoney(item.profit))}
${item.note ? `
${escapeHtml(item.note)}
` : ""}
`).join("")}
`; } function buildCurrentPayload() { const history = loadHistory(); const monthValue = document.getElementById("business-finance-month") ? document.getElementById("business-finance-month").value : currentMonthKey(); const record = normalizeRecord({ month: monthValue, label: monthLabel(monthValue), revenue: document.getElementById("business-finance-revenue") ? document.getElementById("business-finance-revenue").value : 0, planRevenue: document.getElementById("business-finance-plan-revenue") ? document.getElementById("business-finance-plan-revenue").value : 0, planSpend: document.getElementById("business-finance-plan-spend") ? document.getElementById("business-finance-plan-spend").value : 0, payroll: document.getElementById("business-finance-payroll") ? document.getElementById("business-finance-payroll").value : 0, rent: document.getElementById("business-finance-rent") ? document.getElementById("business-finance-rent").value : 0, taxes: document.getElementById("business-finance-taxes") ? document.getElementById("business-finance-taxes").value : 0, adSpend: document.getElementById("business-finance-ad-spend") ? document.getElementById("business-finance-ad-spend").value : 0, otherSpend: document.getElementById("business-finance-other-spend") ? document.getElementById("business-finance-other-spend").value : 0, note: document.getElementById("business-finance-note") ? document.getElementById("business-finance-note").value : "", }); const previous = history.find((item) => item.month < record.month) || null; return { record, history, expenseNorm: buildExpenseNorm(record, previous), planFact: buildPlanFact(record), filename: `ya-stats-business-finance-${record.month || "month"}.pdf`, }; } async function downloadPdf() { const payload = buildCurrentPayload(); const response = await fetch("/auth/site/business-finance-pdf", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "same-origin", body: JSON.stringify(payload), }); if (!response.ok) { const text = await response.text(); throw new Error(text || "Не удалось скачать PDF."); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = payload.filename; document.body.appendChild(link); link.click(); link.remove(); window.setTimeout(() => window.URL.revokeObjectURL(url), 1000); } function renderModal(selectedMonth) { const month = normalizeMonthKey(selectedMonth); const history = loadHistory(); const current = getRecord(month); const previous = history.find((item) => item.month < month) || null; const expenseNorm = buildExpenseNorm(current, previous); const planFact = buildPlanFact(current); const summaryLines = buildSummary(current, previous, expenseNorm); const modal = document.getElementById("business-modal"); const body = document.getElementById("business-modal-body"); if (!modal || !body) return; const kicker = modal.querySelector(".text-yellow-500"); const title = modal.querySelector("h3"); if (kicker) kicker.textContent = "Финансы бизнеса"; if (title) title.textContent = "Расходы, выручка и итог по месяцам"; if (!hasBusinessFinanceAccess()) { body.innerHTML = getBusinessFinanceUpgradeHtml(); return; } body.innerHTML = `

Этот раздел живет отдельно от Яндекс Директа. Вы сами вносите расходы и выручку, а кабинет показывает, остается бизнес в плюсе или уже проседает.

Месяц для заполнения

Все расходы

${formatMoney(current.totalSpend)}

Чистый итог

${formatMoney(current.profit)}

ROI

${current.roi != null ? formatPercent(current.roi) : "—"}

Доля рекламы

${current.adShare != null ? formatPercent(current.adShare) : "—"}

Точка безубыточности

${formatMoney(current.breakEvenRevenue)}

Сколько выручки нужно за месяц, чтобы просто выйти в ноль.

Норма расходов

${escapeHtml(expenseNorm.label)}

${escapeHtml(expenseNorm.description)}

План-факт по выручке

${escapeHtml(planFact.revenueStatus)}

${current.planRevenue > 0 ? `Факт ${escapeHtml(formatMoney(current.revenue))} против плана ${escapeHtml(formatMoney(current.planRevenue))}.` : "Задайте план по выручке, чтобы видеть отклонение."}

План-факт по расходам

${escapeHtml(planFact.spendStatus)}

${current.planSpend > 0 ? `Факт ${escapeHtml(formatMoney(current.totalSpend))} против лимита ${escapeHtml(formatMoney(current.planSpend))}.` : "Задайте лимит расходов, чтобы видеть перерасход."}

График за последние 6 месяцев

Здесь видно не только выручку и все расходы, но и из чего именно эти расходы состоят.

${buildChart(history)}

Что это значит простыми словами

${summaryLines.map((item) => `
${escapeHtml(item)}
`).join("")}

Сравнение по месяцам

${buildHistoryRows(history)}

Что смотреть в первую очередь

Если точка безубыточности выше вашей обычной выручки, бизнесу тяжело держаться в плюсе. Значит, надо или резать расходы, или поднимать продажи.
Если раздел показывает статус “Проверить”, первым делом смотрите, что именно растет быстрее выручки: реклама, зарплаты или прочие траты.
Комментарий к месяцу помогает потом не гадать, почему цифры изменились: акция, новый сотрудник, сезонность, аренда, налоги.
`; const statusBox = document.getElementById("business-finance-status"); if (statusBox && historyLoadWarning) { statusBox.textContent = historyLoadWarning; statusBox.classList.remove("hidden"); } document.getElementById("business-finance-month").addEventListener("change", (event) => { renderModal(event.target.value); }); document.getElementById("business-finance-save-btn").addEventListener("click", async () => { const monthValue = document.getElementById("business-finance-month").value; const statusBox = document.getElementById("business-finance-status"); try { if (statusBox) { statusBox.textContent = ""; statusBox.classList.add("hidden"); } await upsertRecord({ month: monthValue, revenue: document.getElementById("business-finance-revenue").value, planRevenue: document.getElementById("business-finance-plan-revenue").value, planSpend: document.getElementById("business-finance-plan-spend").value, payroll: document.getElementById("business-finance-payroll").value, rent: document.getElementById("business-finance-rent").value, taxes: document.getElementById("business-finance-taxes").value, adSpend: document.getElementById("business-finance-ad-spend").value, otherSpend: document.getElementById("business-finance-other-spend").value, note: document.getElementById("business-finance-note").value, }); renderModal(monthValue); } catch (error) { if (statusBox) { statusBox.textContent = String(error && error.message || "Не удалось сохранить месяц."); statusBox.classList.remove("hidden"); } } }); document.getElementById("business-finance-pdf-btn").addEventListener("click", async () => { const statusBox = document.getElementById("business-finance-status"); try { if (statusBox) { statusBox.textContent = ""; statusBox.classList.add("hidden"); } await downloadPdf(); } catch (error) { window.alert(String(error && error.message || "Не удалось скачать PDF.")); } }); const originalPdfButton = document.getElementById("business-finance-pdf-btn"); if (originalPdfButton) { const cleanPdfButton = originalPdfButton.cloneNode(true); originalPdfButton.replaceWith(cleanPdfButton); cleanPdfButton.addEventListener("click", async () => { const statusBox = document.getElementById("business-finance-status"); if (statusBox) { statusBox.textContent = ""; statusBox.classList.add("hidden"); } try { await downloadPdf(); } catch (error) { let message = String(error && error.message || "Не удалось скачать PDF."); if (message.includes("Not Found") || message.includes("404")) { message = "PDF-модуль пока не опубликован на сервере. Нужно обновить только маршрут финансового PDF."; } else if (message.includes("403")) { message = "PDF по финансам бизнеса доступен только на тарифах Pro и Business."; } if (statusBox) { statusBox.textContent = message; statusBox.classList.remove("hidden"); } } }); } } async function openStandaloneBusinessFinanceModal() { if (typeof window.openModal === "function") { window.openModal("business-modal"); } await ensureBusinessFinanceSubscription(); try { await ensureHistoryLoaded(); } catch (error) { historyLoadWarning = String(error && error.message || "Не удалось загрузить финансы бизнеса."); historyCache = Array.isArray(historyCache) ? historyCache : []; } renderModal(currentMonthKey()); } function bindButtons() { ["open-business-finance-btn", "tool-business-btn"].forEach((id) => { const node = document.getElementById(id); if (!node || node.dataset.businessFinanceBound === "1") return; node.dataset.businessFinanceBound = "1"; node.addEventListener("click", openStandaloneBusinessFinanceModal); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", bindButtons, { once: true }); } else { bindButtons(); } window.openStandaloneBusinessFinanceModal = openStandaloneBusinessFinanceModal; window.openBusinessMetricsModal = openStandaloneBusinessFinanceModal; })();