Обновлены поля: бюджет из Бюджет закупок, исполнение из Сумма поставки, экономия, тип бюджета БКВ/ОБ
This commit is contained in:
parent
56205d6cf4
commit
f27a8b95de
30
index.html
30
index.html
@ -78,7 +78,7 @@
|
||||
<div class="about-card">
|
||||
<div class="about-icon">📈</div>
|
||||
<h3>Данные</h3>
|
||||
<p>Код статьи бюджета, наименование услуг/работ, подразделение-заказчик, плановые суммы и фактическое исполнение</p>
|
||||
<p>Краткий текст материала, подразделение (МВЗ), тип бюджета (БКВ/ОБ), бюджет закупок, сумма поставки, экономия</p>
|
||||
</div>
|
||||
<div class="about-card">
|
||||
<div class="about-icon">🎯</div>
|
||||
@ -117,11 +117,12 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th onclick="sort('articleCode')">Код статьи ↕</th>
|
||||
<th onclick="sort('serviceName')">Наименование услуги ↕</th>
|
||||
<th onclick="sort('serviceName')">Наименование ↕</th>
|
||||
<th onclick="sort('department')">Подразделение ↕</th>
|
||||
<th class="amount" onclick="sort('budgetSum')">Бюджет ↕</th>
|
||||
<th class="amount" onclick="sort('executedSum')">Исполнено ↕</th>
|
||||
<th onclick="sort('budgetType')">Тип бюджета ↕</th>
|
||||
<th class="amount" onclick="sort('budgetSum')">Бюджет закупок, тг ↕</th>
|
||||
<th class="amount" onclick="sort('executedSum')">Сумма поставки, тг ↕</th>
|
||||
<th class="amount" onclick="sort('economy')">Экономия, тг ↕</th>
|
||||
<th class="percent" onclick="sort('execPercent')">Исполнение % ↕</th>
|
||||
<th onclick="sort('status')">Статус ↕</th>
|
||||
</tr>
|
||||
@ -180,14 +181,19 @@
|
||||
function renderStats() {
|
||||
const totalBudget = allData.reduce((sum, r) => sum + (r.budgetSum || 0), 0);
|
||||
const totalExecuted = allData.reduce((sum, r) => sum + (r.executedSum || 0), 0);
|
||||
const totalEconomy = allData.reduce((sum, r) => sum + (r.economy || 0), 0);
|
||||
const avgPercent = totalBudget > 0 ? (totalExecuted / totalBudget) * 100 : 0;
|
||||
const fullyExecuted = allData.filter(r => r.execPercent >= 95).length;
|
||||
const bkvCount = allData.filter(r => r.budgetType === 'БКВ').length;
|
||||
const obCount = allData.filter(r => r.budgetType === 'ОБ').length;
|
||||
|
||||
document.getElementById('stats').innerHTML =
|
||||
'<div class="stat-card"><div class="value">' + formatAmount(totalBudget) + '</div><div class="label">Всего бюджет</div></div>' +
|
||||
'<div class="stat-card"><div class="value">' + formatAmount(totalExecuted) + '</div><div class="label">Исполнено</div></div>' +
|
||||
'<div class="stat-card"><div class="value">' + formatAmount(totalBudget) + '</div><div class="label">Бюджет закупок, тг</div></div>' +
|
||||
'<div class="stat-card"><div class="value">' + formatAmount(totalExecuted) + '</div><div class="label">Сумма поставки, тг</div></div>' +
|
||||
'<div class="stat-card"><div class="value">' + formatAmount(totalEconomy) + '</div><div class="label">Экономия, тг</div></div>' +
|
||||
'<div class="stat-card"><div class="value">' + formatAmount(avgPercent) + '%</div><div class="label">Среднее исполнение</div></div>' +
|
||||
'<div class="stat-card"><div class="value">' + fullyExecuted + '</div><div class="label">Полностью исполнено</div></div>';
|
||||
'<div class="stat-card"><div class="value">' + bkvCount + '</div><div class="label">БКВ</div></div>' +
|
||||
'<div class="stat-card"><div class="value">' + obCount + '</div><div class="label">ОБ</div></div>';
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
@ -205,11 +211,12 @@
|
||||
const statusClass = getStatusClass(r.execPercent || 0);
|
||||
const statusText = getStatusText(r.execPercent || 0);
|
||||
return '<tr>' +
|
||||
'<td>' + (r.articleCode || '-') + '</td>' +
|
||||
'<td>' + (r.serviceName || '-') + '</td>' +
|
||||
'<td>' + (r.department || '-') + '</td>' +
|
||||
'<td>' + (r.budgetType || '-') + '</td>' +
|
||||
'<td class="amount">' + formatAmount(r.budgetSum || 0) + '</td>' +
|
||||
'<td class="amount">' + formatAmount(r.executedSum || 0) + '</td>' +
|
||||
'<td class="amount" style="color: ' + (r.economy > 0 ? '#22c55e' : '#666') + '">' + formatAmount(r.economy || 0) + '</td>' +
|
||||
'<td class="percent">' + formatAmount(r.execPercent || 0) + '%</td>' +
|
||||
'<td><span class="status-badge ' + statusClass + '">' + statusText + '</span></td>' +
|
||||
'</tr>';
|
||||
@ -297,13 +304,14 @@
|
||||
}
|
||||
|
||||
function exportToExcel() {
|
||||
const head = ["Код статьи", "Наименование услуги", "Подразделение", "Бюджет", "Исполнено", "Исполнение %", "Статус"];
|
||||
const head = ["Наименование", "Подразделение", "Тип бюджета", "Бюджет закупок, тг", "Сумма поставки, тг", "Экономия, тг", "Исполнение %", "Статус"];
|
||||
const body = filteredData.map(r => [
|
||||
r.articleCode || '',
|
||||
r.serviceName || '',
|
||||
r.department || '',
|
||||
r.budgetType || '',
|
||||
r.budgetSum || 0,
|
||||
r.executedSum || 0,
|
||||
r.economy || 0,
|
||||
(r.execPercent || 0).toFixed(2),
|
||||
getStatusText(r.execPercent || 0)
|
||||
]);
|
||||
|
||||
32
server.js
32
server.js
@ -25,18 +25,19 @@ function parseExcelData() {
|
||||
|
||||
const findColumn = (names) => {
|
||||
for (const name of names) {
|
||||
const idx = headers.findIndex(h => h.includes(name.toLowerCase()));
|
||||
const idx = headers.findIndex(h => h.toLowerCase().includes(name.toLowerCase()));
|
||||
if (idx !== -1) return idx;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const articleCodeIdx = findColumn(['код статьи', 'статья', 'код']);
|
||||
const serviceNameIdx = findColumn(['наименование', 'услуг', 'работ', 'название']);
|
||||
const departmentIdx = findColumn(['подраздел', 'центр', 'департамент', 'управление']);
|
||||
const budgetSumIdx = findColumn(['бюджет', 'план', 'сумма план']);
|
||||
const executedSumIdx = findColumn(['исполн', 'факт', 'оплачено']);
|
||||
const execPercentIdx = findColumn(['исполнение', '%', 'процент']);
|
||||
const serviceNameIdx = findColumn(['Краткий текст материала']);
|
||||
const budgetSumIdx = findColumn(['Бюджет закупок,тг']);
|
||||
const executedSumIdx = findColumn(['Сумма поставки,тг']);
|
||||
const economyIdx = findColumn(['Экономия от проведенного закупа']);
|
||||
const sppElementIdx = findColumn(['СПП-элемент']);
|
||||
const departmentIdx = findColumn(['Завод', 'МВЗ', 'Текст МВЗ']);
|
||||
const execPercentIdx = findColumn(['% исполнения']);
|
||||
|
||||
const data = [];
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
@ -45,8 +46,17 @@ function parseExcelData() {
|
||||
|
||||
const budgetSum = parseFloat(String(row[budgetSumIdx] || 0).replace(/\s/g, '')) || 0;
|
||||
const executedSum = parseFloat(String(row[executedSumIdx] || 0).replace(/\s/g, '')) || 0;
|
||||
const economy = parseFloat(String(row[economyIdx] || 0).replace(/\s/g, '')) || 0;
|
||||
const sppElement = String(row[sppElementIdx] || '').trim();
|
||||
|
||||
let budgetType = '';
|
||||
if (sppElement.startsWith('I')) {
|
||||
budgetType = 'БКВ';
|
||||
} else if (sppElement.startsWith('O')) {
|
||||
budgetType = 'ОБ';
|
||||
}
|
||||
|
||||
let execPercent = 0;
|
||||
|
||||
if (execPercentIdx !== -1 && row[execPercentIdx]) {
|
||||
execPercent = parseFloat(String(row[execPercentIdx]).replace('%', '').replace(/\s/g, '')) || 0;
|
||||
} else if (budgetSum > 0) {
|
||||
@ -54,15 +64,17 @@ function parseExcelData() {
|
||||
}
|
||||
|
||||
const item = {
|
||||
articleCode: String(row[articleCodeIdx] || '').trim(),
|
||||
serviceName: String(row[serviceNameIdx] || '').trim(),
|
||||
department: String(row[departmentIdx] || '').trim(),
|
||||
budgetType: budgetType,
|
||||
sppElement: sppElement,
|
||||
budgetSum: budgetSum,
|
||||
executedSum: executedSum,
|
||||
economy: economy,
|
||||
execPercent: Math.round(execPercent * 100) / 100
|
||||
};
|
||||
|
||||
if (item.serviceName || item.articleCode) {
|
||||
if (item.serviceName) {
|
||||
data.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user