Освоить основы стратегического управления корпоративными финансами с применением AI и цифровых аналитических инструментов.
Изучить роль CFO в реализации стратегии компании, финансовую стратегию и ключевые показатели эффективности. Освоить подходы к построению KPI и Balanced Scorecard (BSC).
Google Colab, Python, Gemini, GPT, BSC, KPI-модели, финансовые метрики ROI, EVA, CFROI.
Участники смогут разрабатывать стратегические KPI, строить карту целей BSC и использовать AI для поддержки финансовых управленческих решений.
Сформулировать prompt для генерации структуры технического задания по кейсу анализа и прогнозирования кассовых разрывов. В запросе отразить цель проекта, источник данных, ключевые метрики, требования к аналитике и отчетности.
С использованием AI получить структуру чек-листа, включающую блоки формы: описание проекта, данные, расчеты, визуализация, прогнозирование, отчетность. Для каждого блока определить вопросы и варианты ответов.
Создать Google Form с помощью Google Apps Script, автоматически добавив разделы и вопросы на основе сгенерированной структуры. Настроить форму таким образом, чтобы каждый участник мог заполнить индивидуальное техническое задание.
Сопоставить структуру формы с разработанным решением в Google Colab: убедиться, что все элементы ТЗ (метрики, графики, прогнозы, отчеты) отражены в коде и аналитических результатах.
Обеспечить сохранение результатов заполнения формы в Google Sheets с возможностью последующей выгрузки и использования как полноценного технического задания проекта.
Разработан и протестирован рабочий prototype-код в Google Colab для анализа и прогнозирования кассовых разрывов, сформированы графики, аналитические таблицы, прогнозные модели и управленческий dashboard.
Дополнительно создана автоматизированная форма технического задания с использованием Google Apps Script, обеспечивающая структурированную фиксацию требований и результатов проекта.
Google Apps Script — это облачная платформа на базе JavaScript для автоматизации задач в экосистеме Google (Таблицы, Документы, Gmail, Диск, Календарь и др.). Позволяет писать макросы, создавать пользовательские функции, простые веб-приложения и настраивать интеграции между сервисами Google и внешними API. Не требует установки: код хранится и выполняется на серверах Google, запускаясь прямо из браузера.
Ты — senior fullstack developer и эксперт по Google Apps Script.
Разработай production-ready веб-приложение Google Apps Script из двух файлов:
1. Code.gs
2. Index.html
Тема приложения:
«Оценка эффективности Компании»
Цель:
Создать интерактивный финансовый дашборд для анализа прибыли и рентабельности компании за два года на основе вводимых пользователем данных.
Стиль интерфейса:
- темная тема;
- графитовый фон;
- неоновые акценты;
- футуристический стиль;
- премиальный внешний вид;
- адаптивная верстка для desktop и mobile.
Структура интерфейса:
1. Верхний блок:
- крупный заголовок: «Оценка эффективности Компании»
- подзаголовок: «Анализ прибыли и рентабельности за два года»
2. Блок переменных:
Разместить сразу под заголовком, над аналитическими блоками.
Все поля должны быть предзаполнены данными:
Выручка 2024: 4143000000
Выручка 2025: 4796000000
Материальные затраты 2024: 1834000000
Материальные затраты 2025: 2534000000
Нематериальные затраты 2024: 811000000
Нематериальные затраты 2025: 802000000
Амортизация 2024: 479000000
Амортизация 2025: 484000000
Проценты 2024: 190500000
Проценты 2025: 106500000
Налоговая ставка: 20
Активы 2024: 9200000000
Активы 2025: 10800000000
Капитал 2024: 7500000000
Капитал 2025: 8900000000
Под блоком переменных разместить две кнопки:
- «Рассчитать»
- «Очистить»
Кнопки расчета и очистки должны быть только в этом месте.
При нажатии «Рассчитать» одновременно формируются два дашборда:
1. Анализ прибыли
2. Анализ рентабельности
При нажатии «Очистить» очищаются таблицы, графики и аналитические выводы.
3. Блок «Анализ прибыли»
Без слов «Часть 1».
Внутри блока:
- слева гистограмма сравнения показателей прибыли за два года;
- значения столбцов в млн у.е.;
- название гистограммы: «Сравнение показателей прибыли за два года, млн у.е.»;
- справа аналитические выводы;
- ниже таблица расчета.
Расчетные показатели:
EBITDA = Выручка - Материальные затраты - Нематериальные затраты
EBIT = EBITDA - Амортизация
EBT = EBIT - Проценты
E (NP) = EBT × (1 - Налоговая ставка)
Аналитические выводы должны меняться в зависимости от динамики:
- если EBIT снижается, а чистая прибыль растет — указать, что итоговый результат поддержан снижением финансовых расходов, а не ростом операционной эффективности;
- если EBIT и чистая прибыль растут — указать положительную динамику;
- если EBIT и чистая прибыль снижаются — указать ухудшение финансового результата;
- если материальные затраты растут быстрее выручки — указать давление себестоимости на прибыль;
- добавить факторы риска и рекомендации.
4. Блок «Анализ рентабельности»
Без слов «Часть 2».
Внутри блока:
- слева гистограмма сравнения показателей рентабельности за два года;
- значения в процентах;
- название гистограммы: «Сравнение показателей рентабельности за два года, %»;
- справа аналитические выводы;
- ниже таблица расчета.
Расчетные показатели:
ROS = Чистая прибыль / Выручка
ROA = Чистая прибыль / Активы
ROE = Чистая прибыль / Капитал
Чистая прибыль для расчета рентабельности берется из расчета блока «Анализ прибыли».
Аналитические выводы должны меняться в зависимости от динамики:
- если ROS, ROA и ROE снижаются — указать снижение эффективности продаж, активов и капитала;
- если ROS, ROA и ROE растут — указать рост эффективности;
- если динамика смешанная — указать необходимость отдельной проверки маржинальности, загрузки активов и структуры капитала;
- если чистая прибыль растет медленнее выручки, активов или капитала — указать причину снижения рентабельности;
- добавить факторы риска и рекомендации.
5. Экспорт в Excel
В нижней части интерфейса разместить кнопку:
«Загрузить .xlsx»
При нажатии формируется Excel-файл и загружается на компьютер пользователя.
Требования к Excel:
- все отчеты на одном листе;
- премиальный стиль Big4;
- жирный главный заголовок;
- затемненные ячейки названий столбцов;
- перенос слов в ячейках;
- аккуратные границы;
- расширенные колонки;
- таблицы:
1) Анализ прибыли
2) Анализ рентабельности
3) Аналитические выводы по прибыли
4) Аналитические выводы по рентабельности
- имя файла: financial_dashboard_big4.xlsx
Для генерации Excel использовать библиотеку xlsx-js-style.
6. Отправка Excel на email
Под кнопкой загрузки добавить поле email и кнопку:
«Отправить»
При нажатии:
- Excel-файл формируется в браузере;
- передается в Code.gs в base64;
- Code.gs отправляет файл через MailApp.sendEmail;
- тема письма: «Отчет: Оценка эффективности Компании»;
- файл прикладывается как .xlsx.
7. Технические требования
Code.gs должен содержать:
- doGet()
- sendXlsxToEmail(payload)
Index.html должен содержать:
- HTML-разметку;
- CSS;
- JavaScript;
- расчеты;
- построение графиков через Chart.js;
- экспорт Excel через xlsx-js-style;
- отправку файла через google.script.run.
Код должен быть готов к прямой вставке в Google Apps Script.
Не используй серверную базу данных.
Не используй внешние backend-сервисы.
function doGet() {
return HtmlService
.createHtmlOutputFromFile('Index')
.setTitle('Оценка эффективности Компании Global Synergy')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function sendXlsxToEmail(payload) {
if (!payload || !payload.email || !payload.base64) {
throw new Error('Нет email или файла для отправки.');
}
const bytes = Utilities.base64Decode(payload.base64);
const blob = Utilities.newBlob(
bytes,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
payload.filename || 'financial_dashboard_big4.xlsx'
);
MailApp.sendEmail({
to: payload.email,
subject: 'Отчет: Оценка эффективности Компании',
body: 'Во вложении Excel-отчет по анализу прибыли и рентабельности компании.',
attachments: [blob]
});
return 'Файл отправлен на почту: ' + payload.email;
}
<!DOCTYPE html>
<html lang="ru">
<head>
<base target="_top">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Оценка эффективности Компании Global Synergy</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx-js-style/dist/xlsx.bundle.js"></script>
<style>
:root {
--bg: #080d12;
--card: #111923;
--card2: #172433;
--line: #2ce8ff;
--violet: #b84dff;
--green: #35ff9a;
--red: #ff4d6d;
--yellow: #ffd166;
--text: #eaf7ff;
--muted: #9fb5c8;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
background:
radial-gradient(circle at top left, rgba(44,232,255,0.18), transparent 30%),
radial-gradient(circle at bottom right, rgba(184,77,255,0.16), transparent 34%),
var(--bg);
color: var(--text);
}
.app {
max-width: 1400px;
margin: 0 auto;
padding: 28px;
}
.hero {
border: 1px solid var(--line);
border-radius: 24px;
padding: 28px;
margin-bottom: 24px;
background: linear-gradient(135deg, rgba(17,25,35,0.98), rgba(23,36,51,0.9));
box-shadow: 0 0 30px rgba(44,232,255,0.22);
}
h1 {
margin: 0;
font-size: 34px;
text-transform: uppercase;
letter-spacing: .6px;
}
.subtitle {
margin-top: 8px;
color: var(--muted);
font-size: 15px;
}
.section {
background: rgba(17,25,35,0.94);
border: 1px solid rgba(44,232,255,0.35);
border-radius: 22px;
padding: 22px;
margin-bottom: 26px;
box-shadow: 0 0 24px rgba(0,0,0,0.4);
}
.section h2 {
margin: 0 0 18px;
color: var(--line);
text-transform: uppercase;
font-size: 22px;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
}
.field {
background: var(--card2);
border: 1px solid rgba(44,232,255,0.24);
border-radius: 14px;
padding: 12px;
}
label {
display: block;
color: var(--muted);
font-size: 12px;
font-weight: 800;
margin-bottom: 7px;
}
input {
width: 100%;
padding: 11px;
border-radius: 10px;
border: 1px solid #33485e;
background: #071019;
color: var(--text);
font-size: 14px;
outline: none;
}
input:focus {
border-color: var(--line);
box-shadow: 0 0 10px rgba(44,232,255,0.35);
}
.actions {
display: flex;
gap: 12px;
margin-top: 18px;
flex-wrap: wrap;
}
button {
border: none;
border-radius: 12px;
padding: 12px 20px;
cursor: pointer;
font-weight: 900;
text-transform: uppercase;
color: #031018;
background: var(--line);
box-shadow: 0 0 18px rgba(44,232,255,0.35);
}
button.secondary {
background: #263545;
color: var(--text);
box-shadow: none;
border: 1px solid #40576f;
}
button.purple {
background: var(--violet);
color: white;
box-shadow: 0 0 18px rgba(184,77,255,0.35);
}
.dashboard {
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 18px;
margin-top: 16px;
}
.panel {
background: #0d141d;
border: 1px solid rgba(44,232,255,0.28);
border-radius: 18px;
padding: 18px;
}
.panel h3 {
margin: 0 0 12px;
}
canvas {
width: 100% !important;
height: 340px !important;
}
.summary {
line-height: 1.58;
color: #dcefff;
font-size: 14px;
}
.summary b { color: var(--green); }
.risk {
color: var(--red);
font-weight: 900;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 18px;
font-size: 13px;
background: #0b121a;
}
th {
background: #1d2c3c;
color: var(--line);
padding: 9px;
border: 1px solid #344b62;
text-align: left;
}
td {
padding: 8px;
border: 1px solid #26394c;
}
.num {
text-align: right;
white-space: nowrap;
}
.pos {
color: var(--green);
font-weight: 900;
}
.neg {
color: var(--red);
font-weight: 900;
}
.footer-tools {
margin-top: 28px;
padding: 22px;
border: 1px solid rgba(184,77,255,0.35);
border-radius: 22px;
background: rgba(17,25,35,0.95);
}
.email-row {
display: grid;
grid-template-columns: 1fr 180px;
gap: 12px;
margin-top: 14px;
}
.status {
margin-top: 12px;
color: var(--yellow);
font-weight: 800;
}
@media (max-width: 1050px) {
.grid { grid-template-columns: repeat(2, 1fr); }
.dashboard { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
.app { padding: 14px; }
.grid, .email-row { grid-template-columns: 1fr; }
h1 { font-size: 24px; }
}
</style>
</head>
<body>
<div class="app">
<header class="hero">
<h1>Оценка эффективности Компании</h1>
<div class="subtitle">Анализ прибыли и рентабельности за два года</div>
</header>
<section class="section">
<h2>Переменные финансовой модели</h2>
<div class="grid">
<div class="field"><label>Выручка 2024</label><input id="tr24" value="4143000000"></div>
<div class="field"><label>Выручка 2025</label><input id="tr25" value="4796000000"></div>
<div class="field"><label>Материальные затраты 2024</label><input id="mat24" value="1834000000"></div>
<div class="field"><label>Материальные затраты 2025</label><input id="mat25" value="2534000000"></div>
<div class="field"><label>Нематериальные затраты 2024</label><input id="nonmat24" value="811000000"></div>
<div class="field"><label>Нематериальные затраты 2025</label><input id="nonmat25" value="802000000"></div>
<div class="field"><label>Амортизация 2024</label><input id="da24" value="479000000"></div>
<div class="field"><label>Амортизация 2025</label><input id="da25" value="484000000"></div>
<div class="field"><label>Проценты 2024</label><input id="i24" value="190500000"></div>
<div class="field"><label>Проценты 2025</label><input id="i25" value="106500000"></div>
<div class="field"><label>Налоговая ставка, %</label><input id="tax" value="20"></div>
<div class="field"><label>Активы 2024</label><input id="ass24" value="9200000000"></div>
<div class="field"><label>Активы 2025</label><input id="ass25" value="10800000000"></div>
<div class="field"><label>Капитал 2024</label><input id="eq24" value="7500000000"></div>
<div class="field"><label>Капитал 2025</label><input id="eq25" value="8900000000"></div>
</div>
<div class="actions">
<button onclick="calculateAll()">Рассчитать</button>
<button class="secondary" onclick="clearAll()">Очистить</button>
</div>
</section>
<section class="section">
<h2>Анализ прибыли</h2>
<div class="dashboard">
<div class="panel">
<h3>Гистограмма: показатели прибыли, млн у.е.</h3>
<canvas id="profitChart"></canvas>
</div>
<div class="panel">
<h3>Аналитические выводы</h3>
<div id="profitConclusion" class="summary">Нажмите «Рассчитать».</div>
</div>
</div>
<div class="panel">
<h3>Таблица расчета прибыли</h3>
<div id="profitTable"></div>
</div>
</section>
<section class="section">
<h2>Анализ рентабельности</h2>
<div class="dashboard">
<div class="panel">
<h3>Гистограмма: рентабельность, %</h3>
<canvas id="profitabilityChart"></canvas>
</div>
<div class="panel">
<h3>Аналитические выводы</h3>
<div id="profitabilityConclusion" class="summary">Нажмите «Рассчитать».</div>
</div>
</div>
<div class="panel">
<h3>Таблица расчета рентабельности</h3>
<div id="profitabilityTable"></div>
</div>
</section>
<section class="footer-tools">
<button class="purple" onclick="downloadExcel()">Загрузить .xlsx</button>
<div class="email-row">
<input id="email" type="email" placeholder="Введите email для отправки отчета">
<button onclick="sendExcel()">Отправить</button>
</div>
<div id="status" class="status"></div>
</section>
</div>
<script>
let profitChart = null;
let profitabilityChart = null;
let profitRows = [];
let profitabilityRows = [];
let profitText = '';
let profitabilityText = '';
function v(id) {
return Number(String(document.getElementById(id).value).replace(/\s/g, '').replace(',', '.')) || 0;
}
function money(n) {
return new Intl.NumberFormat('ru-RU').format(Math.round(n));
}
function mln(n) {
return +(n / 1000000).toFixed(1);
}
function pct(n) {
return (n * 100).toFixed(2).replace('.', ',') + '%';
}
function deltaClass(n) {
return n >= 0 ? 'pos' : 'neg';
}
function change(a, b) {
return a === 0 ? 0 : (b - a) / a;
}
function buildTable(targetId, rows, percentMode = false) {
let html = `
<table>
<thead>
<tr>
<th>Показатель</th>
<th>31.12.2024</th>
<th>31.12.2025</th>
<th>∆</th>
<th>Темп роста</th>
<th>Прирост</th>
</tr>
</thead>
<tbody>
`;
rows.forEach(r => {
const d = r.y25 - r.y24;
const gr = r.y24 === 0 ? 0 : r.y25 / r.y24;
const inc = gr - 1;
html += `
<tr>
<td><b>${r.name}</b></td>
<td class="num">${percentMode ? pct(r.y24) : money(r.y24)}</td>
<td class="num">${percentMode ? pct(r.y25) : money(r.y25)}</td>
<td class="num ${deltaClass(d)}">${percentMode ? d.toFixed(4).replace('.', ',') : money(d)}</td>
<td class="num">${pct(gr)}</td>
<td class="num ${deltaClass(inc)}">${pct(inc)}</td>
</tr>
`;
});
html += '</tbody></table>';
document.getElementById(targetId).innerHTML = html;
}
function calculateAll() {
calculateProfit();
calculateProfitability();
}
function calculateProfit() {
const tr24 = v('tr24'), tr25 = v('tr25');
const mat24 = v('mat24'), mat25 = v('mat25');
const nonmat24 = v('nonmat24'), nonmat25 = v('nonmat25');
const da24 = v('da24'), da25 = v('da25');
const i24 = v('i24'), i25 = v('i25');
const tax = v('tax') / 100;
const ebitda24 = tr24 - mat24 - nonmat24;
const ebitda25 = tr25 - mat25 - nonmat25;
const ebit24 = ebitda24 - da24;
const ebit25 = ebitda25 - da25;
const ebt24 = ebit24 - i24;
const ebt25 = ebit25 - i25;
const np24 = ebt24 * (1 - tax);
const np25 = ebt25 * (1 - tax);
profitRows = [
{ name: 'EBITDA', y24: ebitda24, y25: ebitda25 },
{ name: 'EBIT', y24: ebit24, y25: ebit25 },
{ name: 'EBT', y24: ebt24, y25: ebt25 },
{ name: 'E (NP)', y24: np24, y25: np25 }
];
buildTable('profitTable', profitRows);
const ebitChange = change(ebit24, ebit25);
const npChange = change(np24, np25);
const matChange = change(mat24, mat25);
const revChange = change(tr24, tr25);
let diagnosis = '';
if (ebitChange < 0 && npChange > 0) {
diagnosis = `
Операционная прибыль снизилась на <b>${pct(ebitChange)}</b>, но чистая прибыль выросла на <b>${pct(npChange)}</b>.
Итоговый результат поддержан снижением финансовых расходов, а не ростом операционной эффективности.
`;
} else if (ebitChange > 0 && npChange > 0) {
diagnosis = `
Компания демонстрирует положительную динамику: операционная прибыль выросла на <b>${pct(ebitChange)}</b>,
чистая прибыль выросла на <b>${pct(npChange)}</b>.
`;
} else if (ebitChange < 0 && npChange < 0) {
diagnosis = `
Компания показывает ухудшение результата: операционная прибыль снизилась на <b>${pct(ebitChange)}</b>,
чистая прибыль снизилась на <b>${pct(npChange)}</b>.
`;
} else {
diagnosis = `
Динамика прибыли смешанная. Требуется проверка структуры затрат, финансовых расходов и налоговой нагрузки.
`;
}
const reason = matChange > revChange
? `Ключевая причина давления на прибыль — материальные затраты растут быстрее выручки: <b>${pct(matChange)}</b> против <b>${pct(revChange)}</b>.`
: `Рост выручки сопоставим или выше роста материальных затрат, что снижает давление на маржу.`;
profitText = `
${diagnosis}<br><br>
${reason}<br><br>
<span class="risk">Факторы риска:</span> снижение операционной маржи, рост себестоимости, зависимость прибыли от неоперационных факторов,
ухудшение инвестиционной привлекательности.<br><br>
<b>Рекомендации:</b><br>
1) пересмотреть ценовую политику;<br>
2) оптимизировать материальные затраты и закупки;<br>
3) повысить производительность процессов;<br>
4) контролировать EBITDA и EBIT как ключевые операционные KPI;<br>
5) внедрить цифровой мониторинг затрат.
`;
document.getElementById('profitConclusion').innerHTML = profitText;
if (profitChart) profitChart.destroy();
profitChart = new Chart(document.getElementById('profitChart'), {
type: 'bar',
data: {
labels: profitRows.map(r => r.name),
datasets: [
{ label: '2024', data: profitRows.map(r => mln(r.y24)), backgroundColor: '#35ff9a' },
{ label: '2025', data: profitRows.map(r => mln(r.y25)), backgroundColor: '#b84dff' }
]
},
options: chartOptions('Сравнение показателей прибыли за два года, млн у.е.')
});
document.getElementById('np24Hidden')?.remove();
}
function calculateProfitability() {
const tr24 = v('tr24'), tr25 = v('tr25');
const mat24 = v('mat24'), mat25 = v('mat25');
const nonmat24 = v('nonmat24'), nonmat25 = v('nonmat25');
const da24 = v('da24'), da25 = v('da25');
const i24 = v('i24'), i25 = v('i25');
const tax = v('tax') / 100;
const np24 = (tr24 - mat24 - nonmat24 - da24 - i24) * (1 - tax);
const np25 = (tr25 - mat25 - nonmat25 - da25 - i25) * (1 - tax);
const ass24 = v('ass24'), ass25 = v('ass25');
const eq24 = v('eq24'), eq25 = v('eq25');
const ros24 = np24 / tr24;
const ros25 = np25 / tr25;
const roa24 = np24 / ass24;
const roa25 = np25 / ass25;
const roe24 = np24 / eq24;
const roe25 = np25 / eq25;
profitabilityRows = [
{ name: 'ROS', y24: ros24, y25: ros25 },
{ name: 'ROA', y24: roa24, y25: roa25 },
{ name: 'ROE', y24: roe24, y25: roe25 }
];
buildTable('profitabilityTable', profitabilityRows, true);
const rosChange = change(ros24, ros25);
const roaChange = change(roa24, roa25);
const roeChange = change(roe24, roe25);
const npChange = change(np24, np25);
const revChange = change(tr24, tr25);
const assChange = change(ass24, ass25);
const eqChange = change(eq24, eq25);
let diagnosis = '';
if (rosChange < 0 && roaChange < 0 && roeChange < 0) {
diagnosis = `
Все ключевые показатели рентабельности снизились:
ROS — <b>${pct(rosChange)}</b>, ROA — <b>${pct(roaChange)}</b>, ROE — <b>${pct(roeChange)}</b>.
`;
} else if (rosChange > 0 && roaChange > 0 && roeChange > 0) {
diagnosis = `
Все показатели рентабельности выросли. Компания повышает эффективность продаж, активов и капитала.
`;
} else {
diagnosis = `
Динамика рентабельности смешанная. Нужно проверить маржинальность продаж, загрузку активов и структуру капитала.
`;
}
const reason = (npChange < revChange || npChange < assChange || npChange < eqChange)
? `
Основная причина ухудшения — чистая прибыль растет медленнее базы сравнения:
прибыль <b>${pct(npChange)}</b>, выручка <b>${pct(revChange)}</b>,
активы <b>${pct(assChange)}</b>, капитал <b>${pct(eqChange)}</b>.
`
: `
Чистая прибыль растет быстрее ключевой базы сравнения, что поддерживает рентабельность.
`;
profitabilityText = `
${diagnosis}<br><br>
${reason}<br><br>
<span class="risk">Факторы риска:</span> снижение эффективности активов, падение отдачи на капитал,
рост капиталоемкости бизнеса, снижение рыночной стоимости компании.<br><br>
<b>Рекомендации:</b><br>
1) снизить себестоимость и расходы периода;<br>
2) повысить оборачиваемость активов;<br>
3) пересмотреть инвестиции в низкоэффективные активы;<br>
4) усилить контроль ROA и ROE;<br>
5) внедрить цифровой мониторинг прибыльности.
`;
document.getElementById('profitabilityConclusion').innerHTML = profitabilityText;
if (profitabilityChart) profitabilityChart.destroy();
profitabilityChart = new Chart(document.getElementById('profitabilityChart'), {
type: 'bar',
data: {
labels: profitabilityRows.map(r => r.name),
datasets: [
{ label: '2024', data: profitabilityRows.map(r => +(r.y24 * 100).toFixed(2)), backgroundColor: '#2ce8ff' },
{ label: '2025', data: profitabilityRows.map(r => +(r.y25 * 100).toFixed(2)), backgroundColor: '#ff4d6d' }
]
},
options: chartOptions('Сравнение показателей рентабельности за два года, %')
});
}
function chartOptions(title) {
return {
responsive: true,
plugins: {
legend: { labels: { color: '#eaf7ff' } },
title: { display: true, text: title, color: '#eaf7ff' }
},
scales: {
x: { ticks: { color: '#eaf7ff' }, grid: { color: '#1e3345' } },
y: { ticks: { color: '#eaf7ff' }, grid: { color: '#1e3345' } }
}
};
}
function clearAll() {
document.getElementById('profitTable').innerHTML = '';
document.getElementById('profitabilityTable').innerHTML = '';
document.getElementById('profitConclusion').innerHTML = 'Нажмите «Рассчитать».';
document.getElementById('profitabilityConclusion').innerHTML = 'Нажмите «Рассчитать».';
profitRows = [];
profitabilityRows = [];
profitText = '';
profitabilityText = '';
if (profitChart) profitChart.destroy();
if (profitabilityChart) profitabilityChart.destroy();
}
function makeWorkbook() {
if (!profitRows.length || !profitabilityRows.length) calculateAll();
const rows = [];
rows.push(['Оценка эффективности Компании']);
rows.push(['Премиальный отчет по анализу прибыли и рентабельности за два года']);
rows.push([]);
rows.push(['Анализ прибыли']);
rows.push(['Показатель', '31.12.2024', '31.12.2025', '∆', 'Темп роста', 'Прирост']);
profitRows.forEach(r => {
const d = r.y25 - r.y24;
const gr = r.y24 === 0 ? 0 : r.y25 / r.y24;
rows.push([r.name, r.y24, r.y25, d, gr, gr - 1]);
});
rows.push([]);
rows.push(['Аналитические выводы по прибыли']);
rows.push([stripHtml(profitText)]);
rows.push([]);
rows.push(['Анализ рентабельности']);
rows.push(['Показатель', '31.12.2024', '31.12.2025', '∆', 'Темп роста', 'Прирост']);
profitabilityRows.forEach(r => {
const d = r.y25 - r.y24;
const gr = r.y24 === 0 ? 0 : r.y25 / r.y24;
rows.push([r.name, r.y24, r.y25, d, gr, gr - 1]);
});
rows.push([]);
rows.push(['Аналитические выводы по рентабельности']);
rows.push([stripHtml(profitabilityText)]);
const ws = XLSX.utils.aoa_to_sheet(rows);
ws['!cols'] = [
{ wch: 34 },
{ wch: 20 },
{ wch: 20 },
{ wch: 20 },
{ wch: 16 },
{ wch: 16 }
];
ws['!rows'] = rows.map((_, i) => {
if ([0, 1].includes(i)) return { hpt: 28 };
return { hpt: 24 };
});
const range = XLSX.utils.decode_range(ws['!ref']);
for (let r = range.s.r; r <= range.e.r; r++) {
for (let c = range.s.c; c <= range.e.c; c++) {
const cellRef = XLSX.utils.encode_cell({ r, c });
if (!ws[cellRef]) continue;
ws[cellRef].s = {
font: { name: 'Arial', sz: 11, color: { rgb: '1F2937' } },
alignment: { vertical: 'center', wrapText: true },
border: {
top: { style: 'thin', color: { rgb: 'D1D5DB' } },
bottom: { style: 'thin', color: { rgb: 'D1D5DB' } },
left: { style: 'thin', color: { rgb: 'D1D5DB' } },
right: { style: 'thin', color: { rgb: 'D1D5DB' } }
}
};
}
}
styleMergedTitle(ws, 'A1:F1', '0B1F33', 'FFFFFF', 18);
styleMergedTitle(ws, 'A2:F2', '102A43', 'DCEBFF', 12);
const sectionRows = [3, 11];
sectionRows.forEach(r => styleRow(ws, r, '123B5D', 'FFFFFF', true));
const headerRows = [4, 12];
headerRows.forEach(r => styleRow(ws, r, '1F2937', 'FFFFFF', true));
const conclusionRows = [9, 17];
conclusionRows.forEach(r => styleRow(ws, r, '111827', 'FFFFFF', true));
ws['!merges'] = [
XLSX.utils.decode_range('A1:F1'),
XLSX.utils.decode_range('A2:F2'),
XLSX.utils.decode_range('A10:F10'),
XLSX.utils.decode_range('A18:F18')
];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Dashboard');
return wb;
}
function styleMergedTitle(ws, range, fill, color, size) {
const decoded = XLSX.utils.decode_range(range);
for (let c = decoded.s.c; c <= decoded.e.c; c++) {
const ref = XLSX.utils.encode_cell({ r: decoded.s.r, c });
if (!ws[ref]) ws[ref] = { t: 's', v: '' };
ws[ref].s = {
font: { name: 'Arial', sz: size, bold: true, color: { rgb: color } },
fill: { fgColor: { rgb: fill } },
alignment: { horizontal: 'center', vertical: 'center', wrapText: true }
};
}
}
function styleRow(ws, rowIndex, fill, color, bold) {
for (let c = 0; c <= 5; c++) {
const ref = XLSX.utils.encode_cell({ r: rowIndex, c });
if (!ws[ref]) continue;
ws[ref].s = {
font: { name: 'Arial', sz: 11, bold: bold, color: { rgb: color } },
fill: { fgColor: { rgb: fill } },
alignment: { horizontal: 'center', vertical: 'center', wrapText: true },
border: {
top: { style: 'thin', color: { rgb: '9CA3AF' } },
bottom: { style: 'thin', color: { rgb: '9CA3AF' } },
left: { style: 'thin', color: { rgb: '9CA3AF' } },
right: { style: 'thin', color: { rgb: '9CA3AF' } }
}
};
}
}
function stripHtml(html) {
const div = document.createElement('div');
div.innerHTML = html || '';
return div.textContent || div.innerText || '';
}
function downloadExcel() {
const wb = makeWorkbook();
XLSX.writeFile(wb, 'financial_dashboard_big4.xlsx');
}
function sendExcel() {
const email = document.getElementById('email').value.trim();
const status = document.getElementById('status');
if (!email) {
status.textContent = 'Введите email.';
return;
}
const wb = makeWorkbook();
const base64 = XLSX.write(wb, { bookType: 'xlsx', type: 'base64' });
status.textContent = 'Отправка файла...';
google.script.run
.withSuccessHandler(msg => status.textContent = msg)
.withFailureHandler(err => status.textContent = 'Ошибка отправки: ' + err.message)
.sendXlsxToEmail({
email: email,
base64: base64,
filename: 'financial_dashboard_big4.xlsx'
});
}
calculateAll();
</script>
</body>
</html>