nuzhno-sdelat-sayt-ya-buhgal/server.js

292 lines
10 KiB
JavaScript
Raw Permalink 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 formidable = require('formidable');
const PORT = process.env.PORT || 3000;
const DATA_FILE = path.join(__dirname, 'data.json');
function readData() {
try {
return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
} catch (e) {
return { employees: [], decrees: [], payments: [], operations: [] };
}
}
function writeData(data) {
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}
function parseBody(req) {
return new Promise((resolve) => {
let s = '';
req.on('data', (c) => (s += c));
req.on('end', () => {
try {
resolve(JSON.parse(s || '{}'));
} catch (_) {
resolve({});
}
});
});
}
function sendJSON(res, data, status = 200) {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
http.createServer(async (req, res) => {
const url = req.url.split('?')[0];
if (req.method === 'GET' && url === '/api/data') {
const data = readData();
return sendJSON(res, data);
}
if (req.method === 'POST' && url === '/api/data') {
const body = await parseBody(req);
writeData(body);
return sendJSON(res, { ok: true });
}
if (req.method === 'POST' && url === '/api/upload-sap') {
const form = formidable({ uploadDir: __dirname, keepExtensions: true });
form.parse(req, async (err, fields, files) => {
if (err) {
return sendJSON(res, { error: err.message }, 400);
}
const file = files.file && files.file[0];
if (!file) {
return sendJSON(res, { error: 'Нет файла' }, 400);
}
try {
const wb = XLSX.readFile(file.filepath);
const sheet = wb.Sheets[wb.SheetNames[0]];
const data = XLSX.utils.sheet_to_json(sheet);
fs.unlinkSync(file.filepath);
const currentData = readData();
let imported = 0;
data.forEach(row => {
const tabNum = row['Табельный номер'] || row['Таб.номер'] || row['tab_num'];
if (tabNum) {
const existing = currentData.employees.find(e => e.tabNum === String(tabNum));
if (existing) {
existing.name = row['ФИО'] || row['FIO'] || existing.name;
existing.iin = row['ИИН'] || row['IIN'] || existing.iin;
existing.salary = parseFloat(row['ЧистыйДоход'] || row['Чистый доход'] || row['salary'] || existing.salary);
} else {
currentData.employees.push({
id: Date.now() + Math.random(),
tabNum: String(tabNum),
name: row['ФИО'] || row['FIO'] || 'Не указано',
iin = row['ИИН'] || row['IIN'] || '',
salary: parseFloat(row['ЧистыйДоход'] || row['Чистый доход'] || row['salary'] || 0)
});
imported++;
}
}
});
writeData(currentData);
sendJSON(res, { ok: true, imported, total: currentData.employees.length });
} catch (err) {
sendJSON(res, { error: err.message }, 400);
}
});
return;
}
if (req.method === 'POST' && url === '/api/upload-payment') {
const form = formidable({ uploadDir: __dirname, keepExtensions: true });
form.parse(req, async (err, fields, files) => {
if (err) {
return sendJSON(res, { error: err.message }, 400);
}
const file = files.file && files.file[0];
if (!file) {
return sendJSON(res, { error: 'Нет файла' }, 400);
}
try {
const wb = XLSX.readFile(file.filepath);
const sheet = wb.Sheets[wb.SheetNames[0]];
const data = XLSX.utils.sheet_to_json(sheet);
fs.unlinkSync(file.filepath);
const currentData = readData();
let imported = 0;
data.forEach(row => {
const decreeNumber = row['Номер постановления'] || row['decree_number'];
if (decreeNumber) {
const decree = currentData.decrees.find(d => d.number === String(decreeNumber));
if (decree) {
currentData.payments.push({
id: Date.now() + Math.random(),
decreeId: decree.id,
decreeNumber: String(decreeNumber),
amount: parseFloat(row['Сумма'] || row['amount'] || 0),
date: row['Дата'] || row['date'] || new Date().toISOString().split('T')[0],
period: fields.period || new Date().toISOString().slice(0, 7)
});
decree.paidAmount = (decree.paidAmount || 0) + parseFloat(row['Сумма'] || row['amount'] || 0);
if (decree.paidAmount >= decree.amount) {
decree.status = 'closed';
}
imported++;
}
}
});
writeData(currentData);
sendJSON(res, { ok: true, imported });
} catch (err) {
sendJSON(res, { error: err.message }, 400);
}
});
return;
}
if (req.method === 'POST' && url === '/api/export-bank') {
const body = await parseBody(req);
const data = readData();
const exportData = [];
data.employees.forEach(emp => {
const decrees = data.decrees.filter(d => d.employeeId === emp.id && d.status === 'active');
if (decrees.length > 0) {
const maxDeduction = emp.salary * 0.5;
const alimonyDecrees = decrees.filter(d => d.type === 'alimony');
const otherDecrees = decrees.filter(d => d.type === 'other');
let allocated = 0;
alimonyDecrees.forEach(d => {
const remaining = d.amount - (d.paidAmount || 0);
const amount = Math.min(remaining, maxDeduction * 0.5);
if (amount > 0) {
exportData.push({
'Таб.номер': emp.tabNum,
'ФИО': emp.name,
'ИИН': emp.iin,
'Постановление': d.number,
'Тип': 'Алименты',
'Сумма удержания': amount.toFixed(2)
});
allocated += amount;
}
});
const remainingForOther = maxDeduction - allocated;
if (remainingForOther > 0 && otherDecrees.length > 0) {
const perOther = remainingForOther / otherDecrees.length;
otherDecrees.forEach(d => {
const remaining = d.amount - (d.paidAmount || 0);
const amount = Math.min(remaining, perOther);
if (amount > 0) {
exportData.push({
'Таб.номер': emp.tabNum,
'ФИО': emp.name,
'ИИН': emp.iin,
'Постановление': d.number,
'Тип': 'Штрафы',
'Сумма удержания': amount.toFixed(2)
});
}
});
}
}
});
if (exportData.length === 0) {
return sendJSON(res, { error: 'Нет данных' }, 400);
}
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Банк');
const filename = `bank_export_${body.period || new Date().toISOString().slice(0, 7)}.xlsx`;
const filepath = path.join(__dirname, filename);
XLSX.writeFile(wb, filepath);
sendJSON(res, { ok: true, filename, count: exportData.length });
}
if (req.method === 'POST' && url === '/api/export-report') {
const body = await parseBody(req);
const data = readData();
const exportData = data.decrees.map(d => {
const emp = data.employees.find(e => e.id === d.employeeId);
return {
'Квартал': body.quarter || '',
'Постановление': d.number,
'Дата': new Date(d.date).toLocaleDateString('ru-RU'),
'Сотрудник': emp ? emp.name : '',
'ИИН': emp ? emp.iin : '',
'Тип': d.type === 'alimony' ? 'Алименты' : 'Штрафы',
'Сумма долга': d.amount.toFixed(2),
'Выплачено': (d.paidAmount || 0).toFixed(2),
'Остаток': (d.amount - (d.paidAmount || 0)).toFixed(2),
'Статус': d.status === 'closed' ? 'Закрыто' : 'Активно'
};
});
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Отчёт');
const filename = `report_${body.quarter || new Date().toISOString().slice(0, 7)}.xlsx`;
const filepath = path.join(__dirname, filename);
XLSX.writeFile(wb, filepath);
sendJSON(res, { ok: true, filename, count: exportData.length });
}
if (req.method === 'GET' && url.startsWith('/api/download/')) {
const filename = decodeURIComponent(url.replace('/api/download/', ''));
const filepath = path.join(__dirname, filename);
if (fs.existsSync(filepath)) {
res.writeHead(200, {
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition': `attachment; filename="${filename}"`
});
fs.createReadStream(filepath).pipe(res);
setTimeout(() => {
try { fs.unlinkSync(filepath); } catch (e) {}
}, 60000);
} else {
sendJSON(res, { error: 'Файл не найден' }, 404);
}
return;
}
let file = url === '/' ? '/index.html' : url;
const full = path.join(__dirname, file);
if (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' });
res.end(fs.readFileSync(full));
} else {
res.writeHead(404);
res.end('Not found');
}
}).listen(PORT, () => {
console.log('Сервер запущен на порту ' + PORT);
});