// 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;