// admin-doc.js const express = require('express'); const router = express.Router(); const multer = require('multer'); const path = require('path'); const fs = require('fs'); // Middleware для проверки прав администратора const requireAdmin = (req, res, next) => { if (!req.session.user || req.session.user.role !== 'admin') { return res.status(403).json({ error: 'Недостаточно прав' }); } next(); }; // Настройка загрузки файлов для шаблонов документов const storage = multer.diskStorage({ destination: (req, file, cb) => { const uploadDir = path.join(__dirname, 'data', 'uploads', 'templates'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } cb(null, uploadDir); }, filename: (req, file, cb) => { const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`; cb(null, uniqueName); } }); const upload = multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024 }, // 10MB fileFilter: (req, file, cb) => { const allowedTypes = ['.doc', '.docx', '.pdf', '.txt', '.rtf']; const ext = path.extname(file.originalname).toLowerCase(); if (allowedTypes.includes(ext)) { cb(null, true); } else { cb(new Error('Недопустимый тип файла. Разрешены: .doc, .docx, .pdf, .txt, .rtf')); } } }); // Получение всех типов документов router.get('/admin/doc/types', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const query = ` SELECT dt.*, COUNT(DISTINCT a.id) as approval_stages_count, COUNT(DISTINCT d.id) as documents_count FROM document_types dt LEFT JOIN approval_stages a ON dt.id = a.document_type_id LEFT JOIN documents d ON dt.id = d.document_type_id GROUP BY dt.id ORDER BY dt.name `; db.all(query, [], (err, types) => { if (err) { console.error('Ошибка получения типов документов:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } res.json(types); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Получение конкретного типа документа router.get('/admin/doc/types/:id', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const typeId = req.params.id; const query = ` SELECT dt.* FROM document_types dt WHERE dt.id = ? `; db.get(query, [typeId], (err, docType) => { if (err) { console.error('Ошибка получения типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (!docType) { return res.status(404).json({ error: 'Тип документа не найден' }); } res.json(docType); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Создание нового типа документа router.post('/admin/doc/types', requireAdmin, upload.single('template'), async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const { name, description } = req.body; if (!name) { return res.status(400).json({ error: 'Название типа документа обязательно' }); } // Проверяем, существует ли тип с таким названием db.get("SELECT id FROM document_types WHERE name = ?", [name], (err, existing) => { if (err) { console.error('Ошибка проверки типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (existing) { return res.status(400).json({ error: 'Тип документа с таким названием уже существует' }); } const templatePath = req.file ? req.file.path : null; const query = ` INSERT INTO document_types (name, description, template_path) VALUES (?, ?, ?) `; db.run(query, [name, description || '', templatePath], function(err) { if (err) { console.error('Ошибка создания типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } res.json({ success: true, message: 'Тип документа создан', typeId: this.lastID }); }); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Обновление типа документа router.put('/admin/doc/types/:id', requireAdmin, upload.single('template'), async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const typeId = req.params.id; const { name, description } = req.body; if (!name) { return res.status(400).json({ error: 'Название типа документа обязательно' }); } // Проверяем, существует ли другой тип с таким названием db.get("SELECT id FROM document_types WHERE name = ? AND id != ?", [name, typeId], (err, existing) => { if (err) { console.error('Ошибка проверки типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (existing) { return res.status(400).json({ error: 'Тип документа с таким названием уже существует' }); } // Получаем текущий тип для удаления старого файла db.get("SELECT template_path FROM document_types WHERE id = ?", [typeId], (err, currentType) => { if (err) { console.error('Ошибка получения типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } let templatePath = currentType?.template_path; // Если загружен новый файл if (req.file) { // Удаляем старый файл, если он существует if (currentType?.template_path && fs.existsSync(currentType.template_path)) { fs.unlinkSync(currentType.template_path); } templatePath = req.file.path; } const query = ` UPDATE document_types SET name = ?, description = ?, template_path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `; db.run(query, [name, description || '', templatePath, typeId], function(err) { if (err) { console.error('Ошибка обновления типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Тип документа не найден' }); } res.json({ success: true, message: 'Тип документа обновлен' }); }); }); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Удаление типа документа router.delete('/admin/doc/types/:id', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const typeId = req.params.id; // Проверяем, есть ли документы этого типа db.get("SELECT COUNT(*) as count FROM documents WHERE document_type_id = ?", [typeId], (err, result) => { if (err) { console.error('Ошибка проверки документов:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (result.count > 0) { return res.status(400).json({ error: 'Нельзя удалить тип документа, так как есть документы этого типа' }); } // Получаем путь к файлу шаблона db.get("SELECT template_path FROM document_types WHERE id = ?", [typeId], (err, docType) => { if (err) { console.error('Ошибка получения типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } // Удаляем файл шаблона, если он существует if (docType?.template_path && fs.existsSync(docType.template_path)) { fs.unlinkSync(docType.template_path); } // Сначала удаляем этапы согласования db.run("DELETE FROM approval_stages WHERE document_type_id = ?", [typeId], (err) => { if (err) { console.error('Ошибка удаления этапов согласования:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } // Затем удаляем тип документа db.run("DELETE FROM document_types WHERE id = ?", [typeId], function(err) { if (err) { console.error('Ошибка удаления типа документа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Тип документа не найден' }); } res.json({ success: true, message: 'Тип документа удален' }); }); }); }); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Получение этапов согласования для типа документа router.get('/admin/doc/types/:typeId/stages', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const typeId = req.params.typeId; const query = ` SELECT a.*, u.name as approver_name FROM approval_stages a LEFT JOIN users u ON a.approver_user_id = u.id WHERE a.document_type_id = ? ORDER BY a.stage_number `; db.all(query, [typeId], (err, stages) => { if (err) { console.error('Ошибка получения этапов согласования:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } res.json(stages || []); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Добавление этапа согласования router.post('/admin/doc/types/:typeId/stages', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const typeId = req.params.typeId; const { stage_number, stage_name, approver_role, approver_user_id, is_required = true, can_edit = false, can_comment = true, deadline_days } = req.body; if (!stage_number || !stage_name) { return res.status(400).json({ error: 'Номер и название этапа обязательны' }); } if (!approver_role && !approver_user_id) { return res.status(400).json({ error: 'Укажите роль или конкретного пользователя для согласования' }); } // Проверяем, существует ли этап с таким номером db.get("SELECT id FROM approval_stages WHERE document_type_id = ? AND stage_number = ?", [typeId, stage_number], (err, existing) => { if (err) { console.error('Ошибка проверки этапа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (existing) { return res.status(400).json({ error: 'Этап с таким номером уже существует' }); } const query = ` INSERT INTO approval_stages (document_type_id, stage_number, stage_name, approver_role, approver_user_id, is_required, can_edit, can_comment, deadline_days) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `; db.run(query, [ typeId, stage_number, stage_name, approver_role || null, approver_user_id || null, is_required ? 1 : 0, can_edit ? 1 : 0, can_comment ? 1 : 0, deadline_days ], function(err) { if (err) { console.error('Ошибка создания этапа согласования:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } res.json({ success: true, message: 'Этап согласования добавлен', stageId: this.lastID }); }); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Обновление этапа согласования router.put('/admin/doc/types/:typeId/stages/:stageId', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const typeId = req.params.typeId; const stageId = req.params.stageId; const { stage_number, stage_name, approver_role, approver_user_id, is_required, can_edit, can_comment, deadline_days } = req.body; if (!stage_number || !stage_name) { return res.status(400).json({ error: 'Номер и название этапа обязательны' }); } // Проверяем, существует ли другой этап с таким номером db.get("SELECT id FROM approval_stages WHERE document_type_id = ? AND stage_number = ? AND id != ?", [typeId, stage_number, stageId], (err, existing) => { if (err) { console.error('Ошибка проверки этапа:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (existing) { return res.status(400).json({ error: 'Этап с таким номером уже существует' }); } const query = ` UPDATE approval_stages SET stage_number = ?, stage_name = ?, approver_role = ?, approver_user_id = ?, is_required = ?, can_edit = ?, can_comment = ?, deadline_days = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND document_type_id = ? `; db.run(query, [ stage_number, stage_name, approver_role || null, approver_user_id || null, is_required ? 1 : 0, can_edit ? 1 : 0, can_comment ? 1 : 0, deadline_days, stageId, typeId ], function(err) { if (err) { console.error('Ошибка обновления этапа согласования:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Этап согласования не найден' }); } res.json({ success: true, message: 'Этап согласования обновлен' }); }); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Удаление этапа согласования router.delete('/admin/doc/types/:typeId/stages/:stageId', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const typeId = req.params.typeId; const stageId = req.params.stageId; db.run("DELETE FROM approval_stages WHERE id = ? AND document_type_id = ?", [stageId, typeId], function(err) { if (err) { console.error('Ошибка удаления этапа согласования:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } if (this.changes === 0) { return res.status(404).json({ error: 'Этап согласования не найден' }); } res.json({ success: true, message: 'Этап согласования удален' }); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Получение всех пользователей для выбора согласующих router.get('/admin/doc/approvers', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const query = ` SELECT id, name, login, email, role FROM users WHERE role IN ('admin', 'teacher') ORDER BY name `; db.all(query, [], (err, users) => { if (err) { console.error('Ошибка получения пользователей:', err); return res.status(500).json({ error: 'Ошибка сервера' }); } res.json(users); }); } catch (error) { console.error('Ошибка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Получение статистики по документам router.get('/admin/doc/stats', requireAdmin, async (req, res) => { try { const { getDb } = require('./database'); const db = getDb(); const stats = await new Promise((resolve, reject) => { db.get(` SELECT COUNT(*) as total_documents, COUNT(CASE WHEN status = 'draft' THEN 1 END) as draft_documents, COUNT(CASE WHEN status = 'in_review' THEN 1 END) as in_review_documents, COUNT(CASE WHEN status = 'approved' THEN 1 END) as approved_documents, COUNT(CASE WHEN status = 'rejected' THEN 1 END) as rejected_documents, COUNT(DISTINCT created_by) as unique_authors, COUNT(DISTINCT document_type_id) as types_used FROM documents `, [], (err, row) => { if (err) reject(err); else resolve(row || {}); }); }); const recentDocuments = await new Promise((resolve, reject) => { db.all(` SELECT d.id, d.title, d.status, d.created_at, u.name as author_name, dt.name as type_name FROM documents d LEFT JOIN users u ON d.created_by = u.id LEFT JOIN document_types dt ON d.document_type_id = dt.id ORDER BY d.created_at DESC LIMIT 10 `, [], (err, rows) => { if (err) reject(err); else resolve(rows || []); }); }); const typeStats = await new Promise((resolve, reject) => { db.all(` SELECT dt.name, COUNT(d.id) as document_count, COUNT(CASE WHEN d.status = 'approved' THEN 1 END) as approved_count, COUNT(CASE WHEN d.status = 'rejected' THEN 1 END) as rejected_count FROM document_types dt LEFT JOIN documents d ON dt.id = d.document_type_id GROUP BY dt.id ORDER BY document_count DESC `, [], (err, rows) => { if (err) reject(err); else resolve(rows || []); }); }); res.json({ stats, recentDocuments, typeStats, timestamp: new Date().toISOString() }); } catch (error) { console.error('Ошибка получения статистики:', error); res.status(500).json({ error: 'Ошибка получения статистики' }); } }); module.exports = router;