953 lines
44 KiB
JavaScript
953 lines
44 KiB
JavaScript
// 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; |