From 1a0698a72bfca53eafd0b9c328710a1a1271829b Mon Sep 17 00:00:00 2001 From: kalugin66 Date: Mon, 26 Jan 2026 22:55:51 +0500 Subject: [PATCH] admin-profiles.html --- admin-doc.js | 563 ++++++++++++++++++++++++++++ database.js | 104 ++++++ doc-endpoints.js | 953 +++++++++++++++++++++++++++++++++++++++++++++++ server.js | 23 ++ 4 files changed, 1643 insertions(+) create mode 100644 admin-doc.js create mode 100644 doc-endpoints.js diff --git a/admin-doc.js b/admin-doc.js new file mode 100644 index 0000000..336b46b --- /dev/null +++ b/admin-doc.js @@ -0,0 +1,563 @@ +// 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; \ No newline at end of file diff --git a/database.js b/database.js index 6a1107c..b974f37 100644 --- a/database.js +++ b/database.js @@ -193,6 +193,110 @@ function createSQLiteTables() { )`); console.log('✅ Таблица для пользовательских настроек инициализирована'); + + // Таблица для типов документов +db.run(`CREATE TABLE IF NOT EXISTS document_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + description TEXT, + template_path TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +)`); + +// Таблица для документов +db.run(`CREATE TABLE IF NOT EXISTS documents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + document_type_id INTEGER NOT NULL, + status TEXT DEFAULT 'draft', + created_by INTEGER NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + approved_at DATETIME, + approved_by INTEGER, + rejected_at DATETIME, + rejected_by INTEGER, + rejection_reason TEXT, + file_path TEXT, + file_name TEXT, + file_size INTEGER, + version INTEGER DEFAULT 1, + parent_document_id INTEGER, + FOREIGN KEY (document_type_id) REFERENCES document_types (id), + FOREIGN KEY (created_by) REFERENCES users (id), + FOREIGN KEY (approved_by) REFERENCES users (id), + FOREIGN KEY (rejected_by) REFERENCES users (id), + FOREIGN KEY (parent_document_id) REFERENCES documents (id) +)`); + +// Таблица для этапов согласования +db.run(`CREATE TABLE IF NOT EXISTS approval_stages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + document_type_id INTEGER NOT NULL, + stage_number INTEGER NOT NULL, + stage_name TEXT NOT NULL, + approver_role TEXT, + approver_user_id INTEGER, + is_required BOOLEAN DEFAULT true, + can_edit BOOLEAN DEFAULT false, + can_comment BOOLEAN DEFAULT true, + deadline_days INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(document_type_id, stage_number), + FOREIGN KEY (document_type_id) REFERENCES document_types (id), + FOREIGN KEY (approver_user_id) REFERENCES users (id) +)`); + +// Таблица для согласования документов +db.run(`CREATE TABLE IF NOT EXISTS document_approvals ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + document_id INTEGER NOT NULL, + stage_id INTEGER NOT NULL, + approver_user_id INTEGER NOT NULL, + status TEXT DEFAULT 'pending', + comments TEXT, + approved_at DATETIME, + rejected_at DATETIME, + deadline DATETIME, + notified_at DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(document_id, stage_id, approver_user_id), + FOREIGN KEY (document_id) REFERENCES documents (id) ON DELETE CASCADE, + FOREIGN KEY (stage_id) REFERENCES approval_stages (id), + FOREIGN KEY (approver_user_id) REFERENCES users (id) +)`); + +// Таблица для комментариев к документам +db.run(`CREATE TABLE IF NOT EXISTS document_comments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + document_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + comment TEXT NOT NULL, + is_internal BOOLEAN DEFAULT false, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (document_id) REFERENCES documents (id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users (id) +)`); + +// Таблица для истории изменений документов +db.run(`CREATE TABLE IF NOT EXISTS document_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + document_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + action TEXT NOT NULL, + changes TEXT, + version INTEGER, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (document_id) REFERENCES documents (id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users (id) +)`); + +console.log('✅ Таблицы для согласования документов созданы'); // Запускаем проверку и обновление структуры таблиц setTimeout(() => { diff --git a/doc-endpoints.js b/doc-endpoints.js new file mode 100644 index 0000000..1832c31 --- /dev/null +++ b/doc-endpoints.js @@ -0,0 +1,953 @@ +// doc-endpoints.js +const express = require('express'); +const router = express.Router(); +const multer = require('multer'); +const path = require('path'); +const fs = require('fs'); + +// Middleware для проверки авторизации +const requireAuth = (req, res, next) => { + if (!req.session.user) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + next(); +}; + +// Настройка загрузки файлов для документов +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + const uploadDir = path.join(__dirname, 'data', 'uploads', 'documents'); + 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: 50 * 1024 * 1024 }, // 50MB + fileFilter: (req, file, cb) => { + const allowedTypes = ['.doc', '.docx', '.pdf', '.txt', '.rtf', '.xls', '.xlsx', '.ppt', '.pptx', '.jpg', '.jpeg', '.png']; + const ext = path.extname(file.originalname).toLowerCase(); + if (allowedTypes.includes(ext)) { + cb(null, true); + } else { + cb(new Error('Недопустимый тип файла')); + } + } +}); + +// Вспомогательная функция для логирования действий с документами +function logDocumentActivity(db, documentId, userId, action, changes = '') { + db.run( + "INSERT INTO document_history (document_id, user_id, action, changes) VALUES (?, ?, ?, ?)", + [documentId, userId, action, changes] + ); +} + +// Получение доступных типов документов для создания +router.get('/api/doc/types', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + + const query = ` + SELECT dt.*, + COUNT(DISTINCT a.id) as stages_count + FROM document_types dt + LEFT JOIN approval_stages a ON dt.id = a.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.post('/api/documents', requireAuth, upload.single('file'), async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const { title, description, document_type_id } = req.body; + const userId = req.session.user.id; + + if (!title || !document_type_id) { + return res.status(400).json({ error: 'Название и тип документа обязательны' }); + } + + if (!req.file) { + return res.status(400).json({ error: 'Файл документа обязателен' }); + } + + // Проверяем существование типа документа + db.get("SELECT id FROM document_types WHERE id = ?", [document_type_id], (err, docType) => { + if (err || !docType) { + return res.status(400).json({ error: 'Тип документа не найден' }); + } + + db.serialize(() => { + // Создаем документ + db.run( + `INSERT INTO documents + (title, description, document_type_id, status, created_by, + file_path, file_name, file_size) + VALUES (?, ?, ?, 'draft', ?, ?, ?, ?)`, + [ + title, + description || '', + document_type_id, + userId, + req.file.path, + req.file.originalname, + req.file.size + ], + function(err) { + if (err) { + console.error('Ошибка создания документа:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + const documentId = this.lastID; + + // Логируем создание + logDocumentActivity(db, documentId, userId, 'DOCUMENT_CREATED', `Создан документ: ${title}`); + + // Получаем этапы согласования для этого типа документа + db.all( + "SELECT * FROM approval_stages WHERE document_type_id = ? ORDER BY stage_number", + [document_type_id], + (err, stages) => { + if (err) { + console.error('Ошибка получения этапов согласования:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + // Создаем записи для согласования + if (stages && stages.length > 0) { + stages.forEach(stage => { + // Определяем пользователя для согласования + let approverUserId = stage.approver_user_id; + + // Если указана роль, находим пользователя с этой ролью + if (stage.approver_role && !approverUserId) { + db.get( + "SELECT id FROM users WHERE role = ? LIMIT 1", + [stage.approver_role], + (err, user) => { + if (user) { + approverUserId = user.id; + } + createApprovalRecord(); + } + ); + } else { + createApprovalRecord(); + } + + function createApprovalRecord() { + if (!approverUserId) { + console.log('⚠️ Не указан согласующий для этапа:', stage.stage_name); + return; + } + + // Устанавливаем дедлайн, если указано + let deadline = null; + if (stage.deadline_days) { + deadline = new Date(); + deadline.setDate(deadline.getDate() + stage.deadline_days); + } + + db.run( + `INSERT INTO document_approvals + (document_id, stage_id, approver_user_id, deadline) + VALUES (?, ?, ?, ?)`, + [documentId, stage.id, approverUserId, deadline] + ); + } + }); + } + + res.json({ + success: true, + message: 'Документ создан', + documentId: documentId + }); + } + ); + } + ); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Получение списка документов пользователя +router.get('/api/documents', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const userId = req.session.user.id; + const { status, type, search } = req.query; + + let query = ` + SELECT DISTINCT d.*, + u.name as author_name, + dt.name as type_name, + dt.id as type_id, + (SELECT COUNT(*) FROM document_approvals da + WHERE da.document_id = d.id AND da.approver_user_id = ? AND da.status = 'pending') as pending_for_me + 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 + LEFT JOIN document_approvals da ON d.id = da.document_id + WHERE 1=1 + `; + + const params = [userId]; + + // Фильтр по статусу + if (status && status !== 'all') { + query += ` AND d.status = ?`; + params.push(status); + } + + // Фильтр по типу + if (type && type !== 'all') { + query += ` AND d.document_type_id = ?`; + params.push(type); + } + + // Поиск + if (search) { + query += ` AND (d.title LIKE ? OR d.description LIKE ?)`; + const searchPattern = `%${search}%`; + params.push(searchPattern, searchPattern); + } + + // Показываем документы, которые создал пользователь или которые ему нужно согласовать + query += ` AND (d.created_by = ? OR da.approver_user_id = ?)`; + params.push(userId, userId); + + query += ` GROUP BY d.id ORDER BY d.created_at DESC`; + + db.all(query, params, async (err, documents) => { + if (err) { + console.error('Ошибка получения документов:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + // Добавляем информацию о согласовании + const documentsWithApprovals = await Promise.all(documents.map(async (doc) => { + const approvals = await new Promise((resolve) => { + db.all(` + SELECT da.*, a.stage_number, a.stage_name, + u.name as approver_name, u.login as approver_login + FROM document_approvals da + LEFT JOIN approval_stages a ON da.stage_id = a.id + LEFT JOIN users u ON da.approver_user_id = u.id + WHERE da.document_id = ? + ORDER BY a.stage_number + `, [doc.id], (err, rows) => { + resolve(rows || []); + }); + }); + + return { + ...doc, + approvals + }; + })); + + res.json(documentsWithApprovals); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Получение информации о конкретном документе +router.get('/api/documents/:id', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + + // Проверяем доступ к документу + db.get(` + SELECT 1 FROM documents d + LEFT JOIN document_approvals da ON d.id = da.document_id + WHERE d.id = ? AND (d.created_by = ? OR da.approver_user_id = ?) + LIMIT 1 + `, [documentId, userId, userId], (err, hasAccess) => { + if (err || !hasAccess) { + return res.status(403).json({ error: 'Доступ к документу запрещен' }); + } + + // Получаем информацию о документе + db.get(` + SELECT d.*, + u.name as author_name, u.login as author_login, + dt.name as type_name, dt.description as type_description, + au.name as approver_name, ru.name as rejector_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 + LEFT JOIN users au ON d.approved_by = au.id + LEFT JOIN users ru ON d.rejected_by = ru.id + WHERE d.id = ? + `, [documentId], async (err, document) => { + if (err || !document) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + // Получаем этапы согласования + const approvals = await new Promise((resolve) => { + db.all(` + SELECT da.*, a.stage_number, a.stage_name, a.can_edit, a.can_comment, + u.name as approver_name, u.login as approver_login, + CASE + WHEN da.approver_user_id = ? THEN 'current' + WHEN da.status = 'pending' AND a.stage_number < ( + SELECT MIN(a2.stage_number) + FROM document_approvals da2 + LEFT JOIN approval_stages a2 ON da2.stage_id = a2.id + WHERE da2.document_id = ? AND da2.status = 'pending' + ) THEN 'completed' + ELSE 'future' + END as user_relation + FROM document_approvals da + LEFT JOIN approval_stages a ON da.stage_id = a.id + LEFT JOIN users u ON da.approver_user_id = u.id + WHERE da.document_id = ? + ORDER BY a.stage_number + `, [userId, documentId, documentId], (err, rows) => { + resolve(rows || []); + }); + }); + + // Получаем комментарии + const comments = await new Promise((resolve) => { + db.all(` + SELECT dc.*, u.name as user_name, u.login as user_login + FROM document_comments dc + LEFT JOIN users u ON dc.user_id = u.id + WHERE dc.document_id = ? + ORDER BY dc.created_at DESC + `, [documentId], (err, rows) => { + resolve(rows || []); + }); + }); + + // Получаем историю + const history = await new Promise((resolve) => { + db.all(` + SELECT dh.*, u.name as user_name + FROM document_history dh + LEFT JOIN users u ON dh.user_id = u.id + WHERE dh.document_id = ? + ORDER BY dh.created_at DESC + LIMIT 20 + `, [documentId], (err, rows) => { + resolve(rows || []); + }); + }); + + // Определяем текущий этап для пользователя + let currentStage = null; + if (document.status === 'in_review') { + currentStage = approvals.find(a => + a.approver_user_id === userId && a.status === 'pending' + ); + } + + // Проверяем права на редактирование + let canEdit = false; + let canApprove = false; + let canComment = true; + + if (document.created_by === userId && document.status === 'draft') { + canEdit = true; + } + + if (currentStage) { + const stageInfo = approvals.find(a => a.id === currentStage.id); + canEdit = stageInfo?.can_edit || false; + canApprove = true; + canComment = stageInfo?.can_comment || true; + } + + res.json({ + document, + approvals, + comments, + history, + permissions: { + canEdit, + canApprove, + canComment, + canDownload: true, + canDelete: document.created_by === userId && document.status === 'draft' + }, + currentStage + }); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Скачивание документа +router.get('/api/documents/:id/download', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + + // Проверяем доступ к документу + db.get(` + SELECT 1 FROM documents d + LEFT JOIN document_approvals da ON d.id = da.document_id + WHERE d.id = ? AND (d.created_by = ? OR da.approver_user_id = ?) + LIMIT 1 + `, [documentId, userId, userId], (err, hasAccess) => { + if (err || !hasAccess) { + return res.status(403).json({ error: 'Доступ к документу запрещен' }); + } + + // Получаем информацию о файле + db.get("SELECT file_path, file_name FROM documents WHERE id = ?", [documentId], (err, doc) => { + if (err || !doc) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + if (!fs.existsSync(doc.file_path)) { + return res.status(404).json({ error: 'Файл не найден' }); + } + + // Устанавливаем заголовки для скачивания + const encodedFileName = encodeURIComponent(doc.file_name); + res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFileName}`); + res.setHeader('Content-Type', 'application/octet-stream'); + + // Отправляем файл + res.sendFile(doc.file_path); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Отправка документа на согласование +router.post('/api/documents/:id/submit', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + + // Проверяем, что документ принадлежит пользователю и имеет статус черновика + db.get("SELECT status, created_by FROM documents WHERE id = ?", [documentId], (err, document) => { + if (err || !document) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + if (document.created_by !== userId) { + return res.status(403).json({ error: 'Недостаточно прав' }); + } + + if (document.status !== 'draft') { + return res.status(400).json({ error: 'Документ уже отправлен на согласование' }); + } + + db.serialize(() => { + // Обновляем статус документа + db.run( + "UPDATE documents SET status = 'in_review', updated_at = CURRENT_TIMESTAMP WHERE id = ?", + [documentId], + function(err) { + if (err) { + console.error('Ошибка обновления статуса:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + // Логируем действие + logDocumentActivity(db, documentId, userId, 'DOCUMENT_SUBMITTED', 'Документ отправлен на согласование'); + + // Отправляем уведомления согласующим первого этапа + db.all(` + SELECT da.approver_user_id, u.email, u.name + FROM document_approvals da + LEFT JOIN users u ON da.approver_user_id = u.id + WHERE da.document_id = ? AND da.status = 'pending' + ORDER BY ( + SELECT stage_number FROM approval_stages WHERE id = da.stage_id + ) LIMIT 1 + `, [documentId], (err, approvers) => { + if (!err && approvers && approvers.length > 0) { + // Здесь можно добавить отправку email уведомлений + console.log(`📄 Документ ${documentId} отправлен на согласование`); + approvers.forEach(approver => { + console.log(` 👤 Уведомить: ${approver.name} (${approver.email})`); + }); + } + }); + + res.json({ success: true, message: 'Документ отправлен на согласование' }); + } + ); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Согласование документа +router.post('/api/documents/:id/approve', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + const { comments } = req.body; + + // Проверяем, что пользователь является текущим согласующим + db.get(` + SELECT da.*, a.stage_number + FROM document_approvals da + LEFT JOIN approval_stages a ON da.stage_id = a.id + WHERE da.document_id = ? AND da.approver_user_id = ? AND da.status = 'pending' + ORDER BY a.stage_number + LIMIT 1 + `, [documentId, userId], (err, approval) => { + if (err || !approval) { + return res.status(403).json({ error: 'Вы не являетесь текущим согласующим' }); + } + + db.serialize(() => { + // Обновляем запись согласования + db.run( + `UPDATE document_approvals + SET status = 'approved', comments = ?, approved_at = CURRENT_TIMESTAMP, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [comments || '', approval.id], + function(err) { + if (err) { + console.error('Ошибка обновления согласования:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + // Логируем действие + logDocumentActivity(db, documentId, userId, 'STAGE_APPROVED', + `Этап ${approval.stage_number} согласован`); + + // Добавляем комментарий, если он есть + if (comments) { + db.run( + `INSERT INTO document_comments (document_id, user_id, comment, is_internal) + VALUES (?, ?, ?, 0)`, + [documentId, userId, comments] + ); + } + + // Проверяем, остались ли этапы для согласования + db.get(` + SELECT COUNT(*) as pending_count + FROM document_approvals + WHERE document_id = ? AND status = 'pending' + `, [documentId], (err, result) => { + if (err) { + console.error('Ошибка проверки оставшихся этапов:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + if (result.pending_count === 0) { + // Все этапы согласованы + db.run( + `UPDATE documents + SET status = 'approved', approved_at = CURRENT_TIMESTAMP, + approved_by = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [userId, documentId], + function(err) { + if (err) { + console.error('Ошибка обновления статуса документа:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + logDocumentActivity(db, documentId, userId, 'DOCUMENT_APPROVED', + 'Документ полностью согласован'); + + res.json({ + success: true, + message: 'Документ полностью согласован', + documentStatus: 'approved' + }); + } + ); + } else { + // Переходим к следующему этапу + db.get(` + SELECT da.approver_user_id, u.email, u.name + FROM document_approvals da + LEFT JOIN users u ON da.approver_user_id = u.id + WHERE da.document_id = ? AND da.status = 'pending' + ORDER BY ( + SELECT stage_number FROM approval_stages WHERE id = da.stage_id + ) LIMIT 1 + `, [documentId], (err, nextApprover) => { + if (!err && nextApprover) { + // Здесь можно добавить отправку уведомления следующему согласующему + console.log(`📄 Переход к следующему согласующему: ${nextApprover.name}`); + } + + res.json({ + success: true, + message: 'Этап согласован', + documentStatus: 'in_review' + }); + }); + } + }); + } + ); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Отклонение документа +router.post('/api/documents/:id/reject', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + const { reason } = req.body; + + if (!reason) { + return res.status(400).json({ error: 'Укажите причину отклонения' }); + } + + // Проверяем, что пользователь является текущим согласующим или создателем + db.get(` + SELECT d.created_by, da.*, a.stage_number + FROM documents d + LEFT JOIN document_approvals da ON d.id = da.document_id AND da.approver_user_id = ? AND da.status = 'pending' + LEFT JOIN approval_stages a ON da.stage_id = a.id + WHERE d.id = ? + `, [userId, documentId], (err, result) => { + if (err) { + console.error('Ошибка проверки прав:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + const canReject = result?.created_by === userId || result?.id; // Создатель или текущий согласующий + + if (!canReject) { + return res.status(403).json({ error: 'Недостаточно прав для отклонения документа' }); + } + + db.serialize(() => { + // Если отклоняет согласующий, отмечаем его этап + if (result.id) { + db.run( + `UPDATE document_approvals + SET status = 'rejected', comments = ?, rejected_at = CURRENT_TIMESTAMP, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [reason, result.id] + ); + + logDocumentActivity(db, documentId, userId, 'STAGE_REJECTED', + `Этап ${result.stage_number} отклонен: ${reason}`); + } + + // Обновляем статус документа + db.run( + `UPDATE documents + SET status = 'rejected', rejected_at = CURRENT_TIMESTAMP, + rejected_by = ?, rejection_reason = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [userId, reason, documentId], + function(err) { + if (err) { + console.error('Ошибка обновления статуса документа:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + // Добавляем комментарий + db.run( + `INSERT INTO document_comments (document_id, user_id, comment, is_internal) + VALUES (?, ?, ?, 0)`, + [documentId, userId, `Документ отклонен: ${reason}`] + ); + + logDocumentActivity(db, documentId, userId, 'DOCUMENT_REJECTED', + `Документ отклонен: ${reason}`); + + res.json({ success: true, message: 'Документ отклонен' }); + } + ); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Добавление комментария к документу +router.post('/api/documents/:id/comments', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + const { comment, is_internal = false } = req.body; + + if (!comment) { + return res.status(400).json({ error: 'Комментарий обязателен' }); + } + + // Проверяем доступ к документу + db.get(` + SELECT 1 FROM documents d + LEFT JOIN document_approvals da ON d.id = da.document_id + WHERE d.id = ? AND (d.created_by = ? OR da.approver_user_id = ?) + LIMIT 1 + `, [documentId, userId, userId], (err, hasAccess) => { + if (err || !hasAccess) { + return res.status(403).json({ error: 'Доступ к документу запрещен' }); + } + + db.run( + `INSERT INTO document_comments (document_id, user_id, comment, is_internal) + VALUES (?, ?, ?, ?)`, + [documentId, userId, comment, is_internal ? 1 : 0], + function(err) { + if (err) { + console.error('Ошибка добавления комментария:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + logDocumentActivity(db, documentId, userId, 'COMMENT_ADDED', 'Добавлен комментарий'); + + res.json({ success: true, message: 'Комментарий добавлен' }); + } + ); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Обновление документа (новая версия) +router.put('/api/documents/:id', requireAuth, upload.single('file'), async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + const { title, description } = req.body; + + // Проверяем права на редактирование + db.get(` + SELECT d.*, da.id as approval_id, a.can_edit + FROM documents d + LEFT JOIN document_approvals da ON d.id = da.document_id AND da.approver_user_id = ? AND da.status = 'pending' + LEFT JOIN approval_stages a ON da.stage_id = a.id + WHERE d.id = ? + `, [userId, documentId], (err, document) => { + if (err || !document) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + const canEdit = (document.created_by === userId && document.status === 'draft') || + (document.approval_id && document.can_edit); + + if (!canEdit) { + return res.status(403).json({ error: 'Недостаточно прав для редактирования' }); + } + + db.serialize(() => { + // Создаем новую версию документа + const newVersion = document.version + 1; + + const updateFields = [ + 'updated_at = CURRENT_TIMESTAMP', + 'version = ?' + ]; + const params = [newVersion]; + + if (title) { + updateFields.push('title = ?'); + params.push(title); + } + + if (description) { + updateFields.push('description = ?'); + params.push(description); + } + + if (req.file) { + // Удаляем старый файл, если он существует + if (document.file_path && fs.existsSync(document.file_path)) { + fs.unlinkSync(document.file_path); + } + + updateFields.push('file_path = ?', 'file_name = ?', 'file_size = ?'); + params.push(req.file.path, req.file.originalname, req.file.size); + } + + params.push(documentId); + + db.run( + `UPDATE documents SET ${updateFields.join(', ')} WHERE id = ?`, + params, + function(err) { + if (err) { + console.error('Ошибка обновления документа:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + // Логируем действие + const changes = []; + if (title && title !== document.title) changes.push(`Название: "${document.title}" -> "${title}"`); + if (description && description !== document.description) changes.push('Описание изменено'); + if (req.file) changes.push('Файл обновлен'); + + logDocumentActivity(db, documentId, userId, 'DOCUMENT_UPDATED', + `Версия ${newVersion}: ${changes.join(', ')}`); + + res.json({ success: true, message: 'Документ обновлен', version: newVersion }); + } + ); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Удаление документа +router.delete('/api/documents/:id', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const documentId = req.params.id; + const userId = req.session.user.id; + + // Проверяем, что документ принадлежит пользователю и имеет статус черновика + db.get("SELECT status, created_by, file_path FROM documents WHERE id = ?", [documentId], (err, document) => { + if (err || !document) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + if (document.created_by !== userId) { + return res.status(403).json({ error: 'Недостаточно прав' }); + } + + if (document.status !== 'draft') { + return res.status(400).json({ error: 'Можно удалять только черновики' }); + } + + db.serialize(() => { + // Удаляем связанные записи + db.run("DELETE FROM document_approvals WHERE document_id = ?", [documentId]); + db.run("DELETE FROM document_comments WHERE document_id = ?", [documentId]); + db.run("DELETE FROM document_history WHERE document_id = ?", [documentId]); + + // Удаляем файл + if (document.file_path && fs.existsSync(document.file_path)) { + fs.unlinkSync(document.file_path); + } + + // Удаляем документ + db.run("DELETE FROM documents WHERE id = ?", [documentId], function(err) { + if (err) { + console.error('Ошибка удаления документа:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + + res.json({ success: true, message: 'Документ удален' }); + }); + }); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +// Получение документов, ожидающих согласования пользователем +router.get('/api/documents/pending', requireAuth, async (req, res) => { + try { + const { getDb } = require('./database'); + const db = getDb(); + const userId = req.session.user.id; + + const query = ` + SELECT d.*, + u.name as author_name, + dt.name as type_name, + a.stage_number, a.stage_name, + da.deadline, + julianday(da.deadline) - julianday('now') as days_until_deadline + FROM document_approvals da + LEFT JOIN documents d ON da.document_id = d.id + LEFT JOIN users u ON d.created_by = u.id + LEFT JOIN document_types dt ON d.document_type_id = dt.id + LEFT JOIN approval_stages a ON da.stage_id = a.id + WHERE da.approver_user_id = ? + AND da.status = 'pending' + AND d.status = 'in_review' + ORDER BY da.deadline ASC, d.created_at DESC + `; + + db.all(query, [userId], (err, documents) => { + if (err) { + console.error('Ошибка получения документов:', err); + return res.status(500).json({ error: 'Ошибка сервера' }); + } + res.json(documents); + }); + } catch (error) { + console.error('Ошибка:', error); + res.status(500).json({ error: 'Ошибка сервера' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server.js b/server.js index d07573f..658d62f 100644 --- a/server.js +++ b/server.js @@ -14,6 +14,10 @@ const { sendTaskNotifications, checkUpcomingDeadlines, getStatusText } = require const { setupUploadMiddleware } = require('./upload-middleware'); const { setupTaskEndpoints } = require('./task-endpoints'); +// admin-doc +const adminDocRouter = require('./admin-doc'); +const docRouter = require('./doc-endpoints'); + const app = express(); const PORT = process.env.PORT || 3000; @@ -39,6 +43,10 @@ app.use(session({ } })); +// Подключаем роутеры для документов +app.use(adminDocRouter); +app.use(docRouter); + // Middleware для проверки готовности сервера app.use((req, res, next) => { if (!serverReady && req.path !== '/health' && req.path !== '/api/health') { @@ -557,6 +565,21 @@ app.get('/admin/profiles', (req, res) => { } res.sendFile(path.join(__dirname, 'public/admin-profiles.html')); }); +// Админ панель для документов +app.get('/admin-doc', (req, res) => { + if (!req.session.user || req.session.user.role !== 'admin') { + return res.status(403).send('Доступ запрещен'); + } + res.sendFile(path.join(__dirname, 'public/admin-doc.html')); +}); + +// Страница согласования документов +app.get('/doc', (req, res) => { + if (!req.session.user) { + return res.redirect('/'); + } + res.sendFile(path.join(__dirname, 'public/doc.html')); +}); // API для получения настроек уведомлений пользователя app.get('/api/user/settings', requireAuth, async (req, res) => {