napishi-programmu-ispolnenie/server.js

170 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 economyIdx = 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 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) {
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);
});