921 lines
45 KiB
JavaScript
921 lines
45 KiB
JavaScript
// 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();
|
||
}
|
||
}; |