170 lines
5.7 KiB
JavaScript
170 lines
5.7 KiB
JavaScript
const http = require("http");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const XLSX = require("xlsx");
|
||
|
||
const PORT = process.env.PORT || 3000;
|
||
const DATA_FILE = path.join(__dirname, "data.json");
|
||
const EXCEL_FILE = path.join(__dirname, "Выгрузка исполнения бюджета закупок.xlsx");
|
||
|
||
function parseExcelData() {
|
||
try {
|
||
if (!fs.existsSync(EXCEL_FILE)) {
|
||
console.log("Excel файл не найден, используем существующие данные");
|
||
return null;
|
||
}
|
||
|
||
const workbook = XLSX.readFile(EXCEL_FILE);
|
||
const sheetName = workbook.SheetNames[0];
|
||
const worksheet = workbook.Sheets[sheetName];
|
||
const rows = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||
|
||
if (rows.length < 2) return [];
|
||
|
||
const headers = rows[0].map(h => String(h || '').trim().toLowerCase());
|
||
|
||
const findColumn = (names) => {
|
||
for (const name of names) {
|
||
const idx = headers.findIndex(h => h.toLowerCase().includes(name.toLowerCase()));
|
||
if (idx !== -1) return idx;
|
||
}
|
||
return -1;
|
||
};
|
||
|
||
const serviceNameIdx = findColumn(['Краткий текст материала']);
|
||
const budgetSumIdx = findColumn(['Сумма с уч.корр']);
|
||
const executedSumIdx = findColumn(['Сумма поставки,тг']);
|
||
const sppElementIdx = findColumn(['СПП-элемент']);
|
||
const departmentIdx = findColumn(['Завод', 'МВЗ', 'Текст МВЗ']);
|
||
const execPercentIdx = findColumn(['% исполнения']);
|
||
|
||
const data = [];
|
||
for (let i = 1; i < rows.length; i++) {
|
||
const row = rows[i];
|
||
if (!row || row.length === 0) continue;
|
||
|
||
const budgetSum = parseFloat(String(row[budgetSumIdx] || 0).replace(/\s/g, '')) || 0;
|
||
const executedSum = parseFloat(String(row[executedSumIdx] || 0).replace(/\s/g, '')) || 0;
|
||
const sppElement = String(row[sppElementIdx] || '').trim();
|
||
|
||
// Экономия = Бюджет - Сумма поставки
|
||
const economy = budgetSum - executedSum;
|
||
|
||
let budgetType = '';
|
||
if (sppElement.startsWith('I')) {
|
||
budgetType = 'БКВ';
|
||
} else if (sppElement.startsWith('O')) {
|
||
budgetType = 'ОБ';
|
||
}
|
||
|
||
let execPercent = 0;
|
||
// Всегда считаем процент из сумм, игнорируя колонку % из Excel
|
||
if (budgetSum > 0) {
|
||
execPercent = (executedSum / budgetSum) * 100;
|
||
}
|
||
|
||
const item = {
|
||
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) {
|
||
data.push(item);
|
||
}
|
||
}
|
||
|
||
console.log("Обработано строк:", data.length);
|
||
return data;
|
||
} catch (error) {
|
||
console.error("Ошибка при чтении Excel:", error.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function ensureDataFile() {
|
||
if (!fs.existsSync(DATA_FILE)) {
|
||
const data = parseExcelData();
|
||
if (data && data.length > 0) {
|
||
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
||
console.log("Создан data.json с", data.length, "записями");
|
||
} else {
|
||
fs.writeFileSync(DATA_FILE, JSON.stringify([], null, 2));
|
||
}
|
||
}
|
||
}
|
||
|
||
function body(req) {
|
||
return new Promise((resolve) => {
|
||
let s = "";
|
||
req.on("data", (c) => (s += c));
|
||
req.on("end", () => {
|
||
try {
|
||
resolve(JSON.parse(s || "{}"));
|
||
} catch (_) {
|
||
resolve({});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
http.createServer(async (req, res) => {
|
||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||
|
||
if (req.method === "OPTIONS") {
|
||
res.writeHead(204);
|
||
return res.end();
|
||
}
|
||
|
||
if (req.method === "GET" && req.url === "/api/data") {
|
||
try {
|
||
const data = JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify(data));
|
||
} catch (error) {
|
||
res.writeHead(500, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ error: error.message }));
|
||
}
|
||
}
|
||
|
||
if (req.method === "POST" && req.url === "/api/reload") {
|
||
try {
|
||
const data = parseExcelData();
|
||
if (data !== null) {
|
||
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ ok: true, count: data.length }));
|
||
} else {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ error: "Не удалось прочитать Excel файл" }));
|
||
}
|
||
} catch (error) {
|
||
res.writeHead(500, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ error: error.message }));
|
||
}
|
||
}
|
||
|
||
let file = req.url === "/" ? "/index.html" : req.url.split("?")[0];
|
||
const full = path.join(__dirname, file);
|
||
|
||
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
|
||
const ext = path.extname(full);
|
||
const type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html";
|
||
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
|
||
return res.end(fs.readFileSync(full));
|
||
}
|
||
|
||
res.writeHead(404);
|
||
res.end("Not found");
|
||
}).listen(PORT, () => {
|
||
ensureDataFile();
|
||
console.log("Сервер запущен на порту " + PORT);
|
||
});
|