diff --git a/api-doc.js b/api-doc.js new file mode 100644 index 0000000..e1324f9 --- /dev/null +++ b/api-doc.js @@ -0,0 +1,921 @@ +// api-doc.js - API endpoints для согласования документов +const path = require('path'); +const fs = require('fs'); +const archiver = require('archiver'); + +module.exports = function(app, db, upload) { + // API для типов документов + app.get('/api/document-types', requireAuth, (req, res) => { + db.all("SELECT * FROM document_types ORDER BY name", [], (err, rows) => { + if (err) { + res.status(500).json({ error: err.message }); + return; + } + res.json(rows); + }); + }); + + // API для создания документа + app.post('/api/documents', requireAuth, upload.array('files', 15), async (req, res) => { + try { + const userId = req.session.user.id; + const { + title, + description, + dueDate, + documentTypeId, + documentNumber, + documentDate, + pagesCount, + urgencyLevel, + comment + } = req.body; + + // Валидация + if (!title || title.trim() === '') { + return res.status(400).json({ error: 'Название документа обязательно' }); + } + + // Получаем пользователей для согласования из .env + const doc1User = process.env.DOC1_USER; + const doc2Users = process.env.DOC2_USERS ? process.env.DOC2_USERS.split(',') : []; + + if (!doc1User) { + return res.status(400).json({ error: 'Не настроен DOC1_USER в системе' }); + } + + // Находим пользователя DOC1 + db.get("SELECT id FROM users WHERE login = ?", [doc1User], async (err, doc1) => { + if (err || !doc1) { + return res.status(400).json({ error: `Пользователь DOC1 (${doc1User}) не найден` }); + } + + // Создаем задачу для документа + db.run(` + INSERT INTO tasks (title, description, due_date, created_by, status, created_at) + VALUES (?, ?, ?, ?, 'active', datetime('now')) + `, [ + `Документ: ${title}`, + description || '', + dueDate || null, + userId + ], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + + const taskId = this.lastID; + + // Создаем запись документа + db.run(` + INSERT INTO documents ( + task_id, document_type_id, document_number, + document_date, pages_count, urgency_level, comment, + created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now')) + `, [ + taskId, + documentTypeId || null, + documentNumber || null, + documentDate || null, + pagesCount || null, + urgencyLevel || 'normal', + comment || null + ], function(err) { + if (err) { + console.error('❌ Ошибка создания документа:', err); + db.run("DELETE FROM tasks WHERE id = ?", [taskId]); + return res.status(500).json({ error: 'Ошибка создания записи документа' }); + } + + const documentId = this.lastID; + + // Назначаем DOC1 для предварительного согласования + db.run(` + INSERT INTO task_assignments (task_id, user_id, status, created_at) + VALUES (?, ?, 'assigned', datetime('now')) + `, [taskId, doc1.id], function(err) { + if (err) { + console.error('❌ Ошибка назначения DOC1:', err); + db.run("DELETE FROM documents WHERE id = ?", [documentId]); + db.run("DELETE FROM tasks WHERE id = ?", [taskId]); + return res.status(500).json({ error: 'Ошибка назначения документа на согласование' }); + } + + const doc1AssignmentId = this.lastID; + + // Загружаем файлы если есть + if (req.files && req.files.length > 0) { + const uploadPromises = req.files.map(file => { + return new Promise((resolve, reject) => { + const filePath = file.path; + const originalName = Buffer.from(file.originalname, 'latin1').toString('utf8'); + + db.run(` + INSERT INTO task_files (task_id, user_id, file_path, original_name, file_size, uploaded_at) + VALUES (?, ?, ?, ?, ?, datetime('now')) + `, [taskId, userId, filePath, originalName, file.size], function(err) { + if (err) { + console.error('❌ Ошибка сохранения файла:', err); + reject(err); + } else { + resolve(); + } + }); + }); + }); + + Promise.all(uploadPromises) + .then(() => { + // Логируем создание документа + const { logActivity } = require('./database'); + logActivity(taskId, userId, 'DOCUMENT_CREATED', `Создан документ: ${title}`); + + // Отправляем уведомление DOC1 + sendDocumentNotification(doc1.id, taskId, 'new_document', title); + + res.json({ + success: true, + message: 'Документ успешно создан и отправлен на предварительное согласование' + }); + }) + .catch(error => { + // Все равно возвращаем успех, так как документ создан + const { logActivity } = require('./database'); + logActivity(taskId, userId, 'DOCUMENT_CREATED', `Создан документ: ${title}`); + + // Отправляем уведомление DOC1 + sendDocumentNotification(doc1.id, taskId, 'new_document', title); + + res.json({ + success: true, + message: 'Документ создан, но были проблемы с загрузкой файлов' + }); + }); + } else { + // Логируем создание документа + const { logActivity } = require('./database'); + logActivity(taskId, userId, 'DOCUMENT_CREATED', `Создан документ: ${title}`); + + // Отправляем уведомление DOC1 + sendDocumentNotification(doc1.id, taskId, 'new_document', title); + + res.json({ + success: true, + message: 'Документ успешно создан и отправлен на предварительное согласование' + }); + } + }); + }); + }); + }); + } catch (error) { + console.error('❌ Общая ошибка создания документа:', error); + res.status(500).json({ + error: 'Ошибка создания документа', + details: error.message + }); + } + }); + + // Получение моих документов + app.get('/api/documents/my', requireAuth, (req, res) => { + const userId = req.session.user.id; + + db.all(` + SELECT + t.id, + t.title, + t.description, + t.due_date, + t.created_at, + t.status, + t.closed_at, + d.id as document_id, + d.document_type_id, + dt.name as document_type_name, + d.document_number, + d.document_date, + d.pages_count, + d.urgency_level, + d.comment, + d.refusal_reason, + ta.status as assignment_status + FROM tasks t + LEFT JOIN documents d ON t.id = d.task_id + LEFT JOIN document_types dt ON d.document_type_id = dt.id + LEFT JOIN task_assignments ta ON t.id = ta.task_id + WHERE t.created_by = ? + AND t.title LIKE 'Документ:%' + ORDER BY t.created_at DESC + `, [userId], async (err, tasks) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + + // Загружаем файлы для каждой задачи + const tasksWithFiles = await Promise.all(tasks.map(async (task) => { + try { + const files = await new Promise((resolve, reject) => { + db.all(` + SELECT tf.*, u.name as user_name + FROM task_files tf + LEFT JOIN users u ON tf.user_id = u.id + WHERE tf.task_id = ? + ORDER BY tf.uploaded_at DESC + `, [task.id], (err, rows) => { + if (err) reject(err); + else resolve(rows || []); + }); + }); + task.files = files || []; + } catch (error) { + task.files = []; + } + return task; + })); + + res.json(tasksWithFiles); + }); + }); + + // Получение документов для согласования (DOC1 и DOC2) + app.get('/api/documents/approval', requireAuth, (req, res) => { + const userId = req.session.user.id; + + // Проверяем, является ли пользователь DOC1 или DOC2 + db.get("SELECT login FROM users WHERE id = ?", [userId], (err, user) => { + if (err || !user) { + return res.status(403).json({ error: 'Пользователь не найден' }); + } + + const userLogin = user.login; + const doc1User = process.env.DOC1_USER; + const doc2Users = process.env.DOC2_USERS ? process.env.DOC2_USERS.split(',') : []; + + const isDoc1 = userLogin === doc1User; + const isDoc2 = doc2Users.includes(userLogin); + + if (!isDoc1 && !isDoc2) { + return res.status(403).json({ error: 'Недостаточно прав для согласования документов' }); + } + + // Определяем статус для фильтрации + let statusFilter = ''; + if (isDoc1) { + // DOC1 видит документы на предварительном согласовании + statusFilter = "AND ta.status IN ('assigned', 'pre_approved')"; + } else if (isDoc2) { + // DOC2 видит предварительно согласованные документы + statusFilter = "AND ta.status = 'pre_approved'"; + } + + db.all(` + SELECT + t.id, + t.title, + t.description, + t.due_date, + t.created_at, + d.id as document_id, + d.document_type_id, + dt.name as document_type_name, + d.document_number, + d.document_date, + d.pages_count, + d.urgency_level, + d.comment, + d.refusal_reason, + ta.status as assignment_status, + u.name as creator_name, + u.login as creator_login + FROM tasks t + JOIN documents d ON t.id = d.task_id + LEFT JOIN document_types dt ON d.document_type_id = dt.id + JOIN task_assignments ta ON t.id = ta.task_id + JOIN users u ON t.created_by = u.id + WHERE ta.user_id = ? + AND t.title LIKE 'Документ:%' + AND t.status = 'active' + AND t.closed_at IS NULL + ${statusFilter} + ORDER BY + CASE d.urgency_level + WHEN 'very_urgent' THEN 1 + WHEN 'urgent' THEN 2 + ELSE 3 + END, + t.due_date ASC, + t.created_at DESC + `, [userId], async (err, tasks) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + + // Загружаем файлы для каждой задачи + const tasksWithFiles = await Promise.all(tasks.map(async (task) => { + try { + const files = await new Promise((resolve, reject) => { + db.all(` + SELECT tf.*, u.name as user_name + FROM task_files tf + LEFT JOIN users u ON tf.user_id = u.id + WHERE tf.task_id = ? + ORDER BY tf.uploaded_at DESC + `, [task.id], (err, rows) => { + if (err) reject(err); + else resolve(rows || []); + }); + }); + task.files = files || []; + } catch (error) { + task.files = []; + } + return task; + })); + + res.json(tasksWithFiles); + }); + }); + }); + + // Обновление статуса документа + app.put('/api/documents/:id/status', requireAuth, (req, res) => { + const documentId = req.params.id; + const { status, comment, refusalReason } = req.body; + const userId = req.session.user.id; + + // Проверяем права пользователя + db.get("SELECT login FROM users WHERE id = ?", [userId], (err, user) => { + if (err || !user) { + return res.status(403).json({ error: 'Пользователь не найден' }); + } + + const userLogin = user.login; + const doc1User = process.env.DOC1_USER; + const doc2Users = process.env.DOC2_USERS ? process.env.DOC2_USERS.split(',') : []; + + const isDoc1 = userLogin === doc1User; + const isDoc2 = doc2Users.includes(userLogin); + + if (!isDoc1 && !isDoc2) { + return res.status(403).json({ error: 'Недостаточно прав' }); + } + + // Проверяем текущий статус документа + db.get(` + SELECT d.*, ta.status as assignment_status, ta.user_id as assignee_id, + t.title, t.created_by + FROM documents d + JOIN tasks t ON d.task_id = t.id + JOIN task_assignments ta ON t.id = ta.task_id + WHERE d.id = ? AND ta.user_id = ? + `, [documentId, userId], (err, document) => { + if (err || !document) { + return res.status(404).json({ error: 'Документ не найден или у вас нет прав' }); + } + + // Валидация переходов статусов + if (isDoc1) { + // DOC1 может только предварительно согласовать или отказать + if (status !== 'pre_approved' && status !== 'refused') { + return res.status(400).json({ error: 'DOC1 может только предварительно согласовать или отказать' }); + } + + if (status === 'pre_approved') { + // Если DOC1 предварительно согласовал, назначаем DOC2 + updateDoc1Status(); + } else { + // Если отказал, просто обновляем статус + updateStatus(); + } + } else if (isDoc2) { + // DOC2 может согласовать или отказать только предварительно согласованные документы + if (document.assignment_status !== 'pre_approved') { + return res.status(400).json({ error: 'DOC2 может работать только с предварительно согласованными документами' }); + } + + if (status === 'approved' || status === 'refused') { + updateStatus(); + } else { + return res.status(400).json({ error: 'DOC2 может только согласовать или отказать' }); + } + } + + function updateDoc1Status() { + // Обновляем статус DOC1 + db.run("UPDATE task_assignments SET status = ? WHERE task_id = ? AND user_id = ?", + [status, document.task_id, userId], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + + // Назначаем DOC2 пользователям + const doc2Logins = doc2Users; + + // Находим ID пользователей DOC2 + const placeholders = doc2Logins.map(() => '?').join(','); + db.all(`SELECT id FROM users WHERE login IN (${placeholders})`, doc2Logins, (err, doc2UsersList) => { + if (err || doc2UsersList.length === 0) { + console.error('DOC2 пользователи не найдены'); + // Все равно считаем успехом + return afterAssignments(); + } + + // Создаем задания для каждого DOC2 + const assignmentPromises = doc2UsersList.map(doc2User => { + return new Promise((resolve, reject) => { + db.run(` + INSERT INTO task_assignments (task_id, user_id, status, created_at) + VALUES (?, ?, 'assigned', datetime('now')) + `, [document.task_id, doc2User.id], function(err) { + if (err) reject(err); + else resolve(); + }); + }); + }); + + Promise.all(assignmentPromises) + .then(() => { + afterAssignments(); + }) + .catch(error => { + console.error('Ошибка назначения DOC2:', error); + afterAssignments(); // Все равно продолжаем + }); + }); + + function afterAssignments() { + // Логируем действие + const { logActivity } = require('./database'); + logActivity(document.task_id, userId, 'STATUS_CHANGED', + `Документ предварительно согласован. Назначен DOC2: ${doc2Logins.join(', ')}`); + + // Сохраняем комментарий + if (comment) { + db.run("UPDATE documents SET comment = COALESCE(comment, '') || '\n' || ? WHERE id = ?", + [`DOC1 (${new Date().toLocaleString('ru-RU')}): ${comment}`, documentId]); + } + + // Отправляем уведомление создателю + sendDocumentNotification(document.created_by, document.task_id, 'pre_approved', document.title); + + // Отправляем уведомления всем DOC2 + doc2Users.forEach(login => { + db.get("SELECT id FROM users WHERE login = ?", [login], (err, doc2User) => { + if (!err && doc2User) { + sendDocumentNotification(doc2User.id, document.task_id, 'new_document_for_doc2', document.title); + } + }); + }); + + res.json({ success: true }); + } + } + ); + } + + function updateStatus() { + // Обновляем статус + db.run("UPDATE task_assignments SET status = ? WHERE task_id = ? AND user_id = ?", + [status, document.task_id, userId], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + + // Сохраняем комментарий и причину отказа + if (comment) { + const role = isDoc1 ? 'DOC1' : 'DOC2'; + const timestamp = new Date().toLocaleString('ru-RU'); + db.run("UPDATE documents SET comment = COALESCE(comment, '') || '\n' || ? WHERE id = ?", + [`${role} (${timestamp}): ${comment}`, documentId]); + } + + if (status === 'refused' && refusalReason) { + db.run("UPDATE documents SET refusal_reason = ? WHERE id = ?", + [refusalReason, documentId]); + } + + // Логируем действие + const { logActivity } = require('./database'); + const actionText = isDoc1 ? + `DOC1: ${status === 'refused' ? 'Отказано' : 'Предварительно согласовано'}` : + `DOC2: ${status === 'refused' ? 'Отказано' : 'Согласовано'}`; + logActivity(document.task_id, userId, 'STATUS_CHANGED', actionText); + + // Отправляем уведомление создателю + if (status === 'approved') { + sendDocumentNotification(document.created_by, document.task_id, 'approved', document.title); + } else if (status === 'refused') { + sendDocumentNotification(document.created_by, document.task_id, 'refused', document.title); + } + + res.json({ success: true }); + } + ); + } + }); + }); + }); + + // Отзыв документа + app.post('/api/documents/:id/cancel', requireAuth, (req, res) => { + const documentId = req.params.id; + const userId = req.session.user.id; + + db.get("SELECT task_id FROM documents WHERE id = ?", [documentId], (err, document) => { + if (err || !document) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + const taskId = document.task_id; + + // Проверяем, что пользователь создатель задачи + db.get("SELECT created_by FROM tasks WHERE id = ?", [taskId], (err, task) => { + if (err || !task) { + return res.status(404).json({ error: 'Задача не найдена' }); + } + + if (parseInt(task.created_by) !== parseInt(userId)) { + return res.status(403).json({ error: 'Вы не являетесь создателем этого документа' }); + } + + // Получаем всех согласующих для уведомлений + db.all("SELECT user_id FROM task_assignments WHERE task_id = ?", [taskId], (err, assignees) => { + if (err) { + console.error('Ошибка получения согласующих:', err); + } + + // Обновляем статус задачи + db.run("UPDATE tasks SET status = 'cancelled', closed_at = datetime('now') WHERE id = ?", [taskId], function(err) { + if (err) { + return res.status(500).json({ error: err.message }); + } + + // Логируем действие + const { logActivity } = require('./database'); + logActivity(taskId, userId, 'STATUS_CHANGED', 'Документ отозван создателем'); + + // Отправляем уведомления согласующим + if (assignees) { + assignees.forEach(assignee => { + if (assignee.user_id !== userId) { + sendDocumentNotification(assignee.user_id, taskId, 'cancelled', 'Документ отозван'); + } + }); + } + + res.json({ success: true }); + }); + }); + }); + }); + }); + + // Получение пакета документов (ZIP архив) + app.get('/api/documents/:id/package', requireAuth, async (req, res) => { + const documentId = req.params.id; + const userId = req.session.user.id; + + // Проверяем доступ к документу + db.get(` + SELECT t.id, t.created_by, t.title, d.document_number + FROM documents d + JOIN tasks t ON d.task_id = t.id + WHERE d.id = ? + `, [documentId], async (err, result) => { + if (err || !result) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + // Проверяем, что пользователь имеет доступ + const isCreator = parseInt(result.created_by) === parseInt(userId); + const isDoc1orDoc2 = await checkIfDoc1orDoc2(userId); + + if (!isCreator && !isDoc1orDoc2) { + return res.status(403).json({ error: 'Недостаточно прав' }); + } + + try { + // Получаем все файлы документа + db.all(` + SELECT tf.* + FROM task_files tf + JOIN tasks t ON tf.task_id = t.id + JOIN documents d ON t.id = d.task_id + WHERE d.id = ? + `, [documentId], async (err, files) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + + if (files.length === 0) { + return res.json({ + success: true, + message: 'Нет файлов для скачивания' + }); + } + + // Создаем временный файл для архива + const tempDir = path.join(__dirname, 'temp'); + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + + const zipFileName = `document_${documentId}_${Date.now()}.zip`; + const zipFilePath = path.join(tempDir, zipFileName); + + const output = fs.createWriteStream(zipFilePath); + const archive = archiver('zip', { + zlib: { level: 9 } + }); + + output.on('close', () => { + // Отправляем файл + res.download(zipFilePath, `Документ_${result.document_number || result.id}.zip`, (err) => { + // Удаляем временный файл после отправки + if (fs.existsSync(zipFilePath)) { + fs.unlinkSync(zipFilePath); + } + }); + }); + + archive.on('error', (err) => { + console.error('Ошибка создания архива:', err); + res.status(500).json({ error: 'Ошибка создания архива' }); + }); + + archive.pipe(output); + + // Добавляем файлы в архив + for (const file of files) { + if (fs.existsSync(file.file_path)) { + const fileName = path.basename(file.original_name); + archive.file(file.file_path, { name: fileName }); + } + } + + // Добавляем информацию о документе как текстовый файл + const docInfo = ` +Документ: ${result.title} +Номер документа: ${result.document_number || 'Не указан'} +Дата создания: ${new Date().toLocaleString('ru-RU')} + +Файлы в архиве: +${files.map((f, i) => `${i + 1}. ${f.original_name} (${formatFileSize(f.file_size)})`).join('\n')} + `; + + archive.append(docInfo, { name: 'Информация_о_документе.txt' }); + + await archive.finalize(); + }); + } catch (error) { + console.error('Ошибка создания пакета:', error); + res.status(500).json({ + success: false, + message: 'Ошибка создания пакета документов' + }); + } + }); + }); + + // Статистика по документам + app.get('/api/documents/stats', requireAuth, (req, res) => { + const userId = req.session.user.id; + + // Проверяем права + db.get("SELECT login FROM users WHERE id = ?", [userId], (err, user) => { + if (err || !user) { + return res.status(403).json({ error: 'Пользователь не найден' }); + } + + const userLogin = user.login; + const doc1User = process.env.DOC1_USER; + const doc2Users = process.env.DOC2_USERS ? process.env.DOC2_USERS.split(',') : []; + + const isDoc1 = userLogin === doc1User; + const isDoc2 = doc2Users.includes(userLogin); + const isAdmin = req.session.user.role === 'admin'; + + if (!isDoc1 && !isDoc2 && !isAdmin) { + return res.status(403).json({ error: 'Недостаточно прав' }); + } + + let statsQuery = ''; + let params = []; + + if (isAdmin) { + // Админ видит все документы + statsQuery = ` + SELECT + COUNT(*) as total, + COUNT(CASE WHEN t.status = 'active' AND t.closed_at IS NULL THEN 1 END) as active, + COUNT(CASE WHEN ta.status = 'pre_approved' THEN 1 END) as pre_approved, + COUNT(CASE WHEN ta.status = 'approved' THEN 1 END) as approved, + COUNT(CASE WHEN ta.status = 'refused' THEN 1 END) as refused, + COUNT(CASE WHEN t.status = 'cancelled' THEN 1 END) as cancelled + FROM tasks t + LEFT JOIN documents d ON t.id = d.task_id + LEFT JOIN task_assignments ta ON t.id = ta.task_id + WHERE t.title LIKE 'Документ:%' + `; + } else { + // DOC1 и DOC2 видят только свои документы + statsQuery = ` + SELECT + COUNT(*) as total, + COUNT(CASE WHEN ta.status = 'assigned' THEN 1 END) as assigned, + COUNT(CASE WHEN ta.status = 'pre_approved' THEN 1 END) as pre_approved, + COUNT(CASE WHEN ta.status = 'approved' THEN 1 END) as approved, + COUNT(CASE WHEN ta.status = 'refused' THEN 1 END) as refused + FROM tasks t + JOIN documents d ON t.id = d.task_id + JOIN task_assignments ta ON t.id = ta.task_id + WHERE t.title LIKE 'Документ:%' + AND t.status = 'active' + AND t.closed_at IS NULL + AND ta.user_id = ? + `; + params = [userId]; + } + + db.get(statsQuery, params, (err, stats) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + + res.json(stats || { + total: 0, + active: 0, + pre_approved: 0, + approved: 0, + refused: 0, + cancelled: 0 + }); + }); + }); + }); + + // Получение истории документа + app.get('/api/documents/:id/history', requireAuth, (req, res) => { + const documentId = req.params.id; + const userId = req.session.user.id; + + // Проверяем доступ к документу + db.get(` + SELECT t.created_by + FROM documents d + JOIN tasks t ON d.task_id = t.id + WHERE d.id = ? + `, [documentId], (err, document) => { + if (err || !document) { + return res.status(404).json({ error: 'Документ не найден' }); + } + + // Проверяем права + const isCreator = parseInt(document.created_by) === parseInt(userId); + const isDoc1orDoc2 = checkIfDoc1orDoc2Sync(userId); + + if (!isCreator && !isDoc1orDoc2) { + return res.status(403).json({ error: 'Недостаточно прав' }); + } + + const taskIdQuery = "SELECT task_id FROM documents WHERE id = ?"; + db.get(taskIdQuery, [documentId], (err, result) => { + if (err || !result) { + return res.status(500).json({ error: 'Ошибка получения истории' }); + } + + const taskId = result.task_id; + + // Получаем историю активности + db.all(` + SELECT al.*, u.name as user_name + FROM activity_logs al + LEFT JOIN users u ON al.user_id = u.id + WHERE al.task_id = ? + ORDER BY al.created_at DESC + `, [taskId], (err, history) => { + if (err) { + return res.status(500).json({ error: err.message }); + } + + // Получаем комментарии из документа + db.get("SELECT comment FROM documents WHERE id = ?", [documentId], (err, doc) => { + if (err) { + doc = { comment: '' }; + } + + const comments = doc.comment ? doc.comment.split('\n').filter(c => c.trim()).map(c => { + return { + text: c, + type: 'comment' + }; + }) : []; + + res.json({ + activity: history || [], + comments: comments + }); + }); + }); + }); + }); + }); + + // Вспомогательные функции + + // Функция для проверки DOC1/DOC2 (асинхронная) + function checkIfDoc1orDoc2(userId) { + return new Promise((resolve, reject) => { + db.get("SELECT login FROM users WHERE id = ?", [userId], (err, user) => { + if (err || !user) { + resolve(false); + return; + } + + const userLogin = user.login; + const doc1User = process.env.DOC1_USER; + const doc2Users = process.env.DOC2_USERS ? process.env.DOC2_USERS.split(',') : []; + + const isDoc1 = userLogin === doc1User; + const isDoc2 = doc2Users.includes(userLogin); + + resolve(isDoc1 || isDoc2); + }); + }); + } + + // Функция для проверки DOC1/DOC2 (синхронная версия) + function checkIfDoc1orDoc2Sync(userId) { + // Эта функция используется в синхронных контекстах + // В реальном приложении нужно быть осторожным с синхронными вызовами + try { + const user = db.getSync("SELECT login FROM users WHERE id = ?", [userId]); + if (!user) return false; + + const userLogin = user.login; + const doc1User = process.env.DOC1_USER; + const doc2Users = process.env.DOC2_USERS ? process.env.DOC2_USERS.split(',') : []; + + const isDoc1 = userLogin === doc1User; + const isDoc2 = doc2Users.includes(userLogin); + + return isDoc1 || isDoc2; + } catch (error) { + return false; + } + } + + // Функция для отправки уведомлений о документах + function sendDocumentNotification(userId, taskId, type, documentTitle) { + try { + const { sendTaskNotifications } = require('./notifications'); + + let message = ''; + switch(type) { + case 'new_document': + message = `Новый документ на согласование: ${documentTitle}`; + break; + case 'new_document_for_doc2': + message = `Новый документ для согласования (DOC2): ${documentTitle}`; + break; + case 'pre_approved': + message = `Документ предварительно согласован: ${documentTitle}`; + break; + case 'approved': + message = `Документ согласован: ${documentTitle}`; + break; + case 'refused': + message = `В согласовании документа отказано: ${documentTitle}`; + break; + case 'cancelled': + message = `Документ отозван создателем: ${documentTitle}`; + break; + default: + message = `Обновление документа: ${documentTitle}`; + } + + sendTaskNotifications(taskId, userId, message); + } catch (error) { + console.error('Ошибка отправки уведомления:', error); + } + } + + // Вспомогательная функция для форматирования размера файла + function formatFileSize(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + } + + // Middleware для аутентификации (можно импортировать из server.js) + function requireAuth(req, res, next) { + if (!req.session.user) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + next(); + } +}; \ No newline at end of file diff --git a/database.js b/database.js index 9de8bdc..428fe0a 100644 --- a/database.js +++ b/database.js @@ -2,6 +2,7 @@ const sqlite3 = require('sqlite3').verbose(); const { Pool } = require('pg'); const path = require('path'); const fs = require('fs'); +const initDocTables = require('./init-doc-tables'); require('dotenv').config(); // Определяем, какую базу использовать @@ -71,12 +72,49 @@ async function initializeDatabase() { await initializeSQLite(); } + // Инициализируем таблицы для документов (после создания основных таблиц) + try { + await initDocTables(db); + } catch (error) { + console.error('⚠️ Ошибка инициализации таблиц документов:', error.message); + } + // Синхронизируем группы пользователей await syncUserGroups(); return db; } +function initializeSQLite() { + return new Promise((resolve, reject) => { + db = new sqlite3.Database(dbPath, (err) => { + if (err) { + console.error('❌ Ошибка подключения к SQLite:', err.message); + reject(err); + return; + } else { + console.log('✅ Подключение к SQLite установлено'); + console.log('📁 База данных расположена:', dbPath); + + // Используем serialize для последовательного выполнения + db.serialize(() => { + // Создаем основные таблицы + createSQLiteTables(); + + // Инициализируем таблицы для документов + initDocTables(db); + + // Добавляем группы по умолчанию + addDefaultGroups(); + + isInitialized = true; + resolve(db); + }); + } + }); + }); +} + function initializeSQLite() { return new Promise((resolve, reject) => { db = new sqlite3.Database(dbPath, (err) => { diff --git a/init-doc-tables.js b/init-doc-tables.js new file mode 100644 index 0000000..24cf7bc --- /dev/null +++ b/init-doc-tables.js @@ -0,0 +1,52 @@ +// init-doc-tables.js - Инициализация таблиц для документов +module.exports = function initDocTables(db) { + console.log('🔧 Инициализация таблиц для документов...'); + + // Создание таблицы типов документов + db.run(` + CREATE TABLE IF NOT EXISTS document_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `); + + // Создание таблицы документов + db.run(` + CREATE TABLE IF NOT EXISTS documents ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id INTEGER NOT NULL, + document_type_id INTEGER, + document_number TEXT, + document_date DATE, + pages_count INTEGER, + urgency_level TEXT DEFAULT 'normal', + comment TEXT, + refusal_reason TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY (document_type_id) REFERENCES document_types(id) + ) + `); + + // Добавляем тестовые типы документов + const docTypes = [ + ['Приказ', 'Распорядительный документ'], + ['Распоряжение', 'Распорядительный документ'], + ['Письмо', 'Деловое письмо'], + ['Служебная записка', 'Внутренний документ'], + ['Договор', 'Юридический документ'], + ['Акт', 'Документ о выполнении работ'], + ['Протокол', 'Документ о проведении собрания'] + ]; + + docTypes.forEach(([name, description]) => { + db.run( + "INSERT OR IGNORE INTO document_types (name, description) VALUES (?, ?)", + [name, description] + ); + }); + + console.log('✅ Таблицы для документов инициализированы'); +}; \ No newline at end of file diff --git a/package.json b/package.json index 4d74cec..88648e7 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dev": "nodemon server.js" }, "dependencies": { + "archiver": "^7.0.1", "bcryptjs": "~2.4.3", "dotenv": "~16.3.1", "express": "^4.21.2", diff --git a/public/doc.html b/public/doc.html index 7fdecb4..62a832e 100644 --- a/public/doc.html +++ b/public/doc.html @@ -3,281 +3,225 @@
-