Files
minicrm/admin-doc.js
2026-01-26 22:55:51 +05:00

563 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// admin-doc.js
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const fs = require('fs');
// Middleware для проверки прав администратора
const requireAdmin = (req, res, next) => {
if (!req.session.user || req.session.user.role !== 'admin') {
return res.status(403).json({ error: 'Недостаточно прав' });
}
next();
};
// Настройка загрузки файлов для шаблонов документов
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadDir = path.join(__dirname, 'data', 'uploads', 'templates');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
const upload = multer({
storage: storage,
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
fileFilter: (req, file, cb) => {
const allowedTypes = ['.doc', '.docx', '.pdf', '.txt', '.rtf'];
const ext = path.extname(file.originalname).toLowerCase();
if (allowedTypes.includes(ext)) {
cb(null, true);
} else {
cb(new Error('Недопустимый тип файла. Разрешены: .doc, .docx, .pdf, .txt, .rtf'));
}
}
});
// Получение всех типов документов
router.get('/admin/doc/types', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const query = `
SELECT dt.*,
COUNT(DISTINCT a.id) as approval_stages_count,
COUNT(DISTINCT d.id) as documents_count
FROM document_types dt
LEFT JOIN approval_stages a ON dt.id = a.document_type_id
LEFT JOIN documents d ON dt.id = d.document_type_id
GROUP BY dt.id
ORDER BY dt.name
`;
db.all(query, [], (err, types) => {
if (err) {
console.error('Ошибка получения типов документов:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
res.json(types);
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Получение конкретного типа документа
router.get('/admin/doc/types/:id', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const typeId = req.params.id;
const query = `
SELECT dt.*
FROM document_types dt
WHERE dt.id = ?
`;
db.get(query, [typeId], (err, docType) => {
if (err) {
console.error('Ошибка получения типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (!docType) {
return res.status(404).json({ error: 'Тип документа не найден' });
}
res.json(docType);
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Создание нового типа документа
router.post('/admin/doc/types', requireAdmin, upload.single('template'), async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const { name, description } = req.body;
if (!name) {
return res.status(400).json({ error: 'Название типа документа обязательно' });
}
// Проверяем, существует ли тип с таким названием
db.get("SELECT id FROM document_types WHERE name = ?", [name], (err, existing) => {
if (err) {
console.error('Ошибка проверки типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (existing) {
return res.status(400).json({ error: 'Тип документа с таким названием уже существует' });
}
const templatePath = req.file ? req.file.path : null;
const query = `
INSERT INTO document_types (name, description, template_path)
VALUES (?, ?, ?)
`;
db.run(query, [name, description || '', templatePath], function(err) {
if (err) {
console.error('Ошибка создания типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
res.json({
success: true,
message: 'Тип документа создан',
typeId: this.lastID
});
});
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Обновление типа документа
router.put('/admin/doc/types/:id', requireAdmin, upload.single('template'), async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const typeId = req.params.id;
const { name, description } = req.body;
if (!name) {
return res.status(400).json({ error: 'Название типа документа обязательно' });
}
// Проверяем, существует ли другой тип с таким названием
db.get("SELECT id FROM document_types WHERE name = ? AND id != ?", [name, typeId], (err, existing) => {
if (err) {
console.error('Ошибка проверки типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (existing) {
return res.status(400).json({ error: 'Тип документа с таким названием уже существует' });
}
// Получаем текущий тип для удаления старого файла
db.get("SELECT template_path FROM document_types WHERE id = ?", [typeId], (err, currentType) => {
if (err) {
console.error('Ошибка получения типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
let templatePath = currentType?.template_path;
// Если загружен новый файл
if (req.file) {
// Удаляем старый файл, если он существует
if (currentType?.template_path && fs.existsSync(currentType.template_path)) {
fs.unlinkSync(currentType.template_path);
}
templatePath = req.file.path;
}
const query = `
UPDATE document_types
SET name = ?, description = ?, template_path = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`;
db.run(query, [name, description || '', templatePath, typeId], function(err) {
if (err) {
console.error('Ошибка обновления типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Тип документа не найден' });
}
res.json({ success: true, message: 'Тип документа обновлен' });
});
});
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Удаление типа документа
router.delete('/admin/doc/types/:id', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const typeId = req.params.id;
// Проверяем, есть ли документы этого типа
db.get("SELECT COUNT(*) as count FROM documents WHERE document_type_id = ?", [typeId], (err, result) => {
if (err) {
console.error('Ошибка проверки документов:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (result.count > 0) {
return res.status(400).json({
error: 'Нельзя удалить тип документа, так как есть документы этого типа'
});
}
// Получаем путь к файлу шаблона
db.get("SELECT template_path FROM document_types WHERE id = ?", [typeId], (err, docType) => {
if (err) {
console.error('Ошибка получения типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
// Удаляем файл шаблона, если он существует
if (docType?.template_path && fs.existsSync(docType.template_path)) {
fs.unlinkSync(docType.template_path);
}
// Сначала удаляем этапы согласования
db.run("DELETE FROM approval_stages WHERE document_type_id = ?", [typeId], (err) => {
if (err) {
console.error('Ошибка удаления этапов согласования:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
// Затем удаляем тип документа
db.run("DELETE FROM document_types WHERE id = ?", [typeId], function(err) {
if (err) {
console.error('Ошибка удаления типа документа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Тип документа не найден' });
}
res.json({ success: true, message: 'Тип документа удален' });
});
});
});
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Получение этапов согласования для типа документа
router.get('/admin/doc/types/:typeId/stages', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const typeId = req.params.typeId;
const query = `
SELECT a.*, u.name as approver_name
FROM approval_stages a
LEFT JOIN users u ON a.approver_user_id = u.id
WHERE a.document_type_id = ?
ORDER BY a.stage_number
`;
db.all(query, [typeId], (err, stages) => {
if (err) {
console.error('Ошибка получения этапов согласования:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
res.json(stages || []);
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Добавление этапа согласования
router.post('/admin/doc/types/:typeId/stages', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const typeId = req.params.typeId;
const {
stage_number,
stage_name,
approver_role,
approver_user_id,
is_required = true,
can_edit = false,
can_comment = true,
deadline_days
} = req.body;
if (!stage_number || !stage_name) {
return res.status(400).json({ error: 'Номер и название этапа обязательны' });
}
if (!approver_role && !approver_user_id) {
return res.status(400).json({ error: 'Укажите роль или конкретного пользователя для согласования' });
}
// Проверяем, существует ли этап с таким номером
db.get("SELECT id FROM approval_stages WHERE document_type_id = ? AND stage_number = ?",
[typeId, stage_number], (err, existing) => {
if (err) {
console.error('Ошибка проверки этапа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (existing) {
return res.status(400).json({ error: 'Этап с таким номером уже существует' });
}
const query = `
INSERT INTO approval_stages
(document_type_id, stage_number, stage_name, approver_role,
approver_user_id, is_required, can_edit, can_comment, deadline_days)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`;
db.run(query, [
typeId, stage_number, stage_name, approver_role || null,
approver_user_id || null, is_required ? 1 : 0,
can_edit ? 1 : 0, can_comment ? 1 : 0, deadline_days
], function(err) {
if (err) {
console.error('Ошибка создания этапа согласования:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
res.json({
success: true,
message: 'Этап согласования добавлен',
stageId: this.lastID
});
});
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Обновление этапа согласования
router.put('/admin/doc/types/:typeId/stages/:stageId', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const typeId = req.params.typeId;
const stageId = req.params.stageId;
const {
stage_number,
stage_name,
approver_role,
approver_user_id,
is_required,
can_edit,
can_comment,
deadline_days
} = req.body;
if (!stage_number || !stage_name) {
return res.status(400).json({ error: 'Номер и название этапа обязательны' });
}
// Проверяем, существует ли другой этап с таким номером
db.get("SELECT id FROM approval_stages WHERE document_type_id = ? AND stage_number = ? AND id != ?",
[typeId, stage_number, stageId], (err, existing) => {
if (err) {
console.error('Ошибка проверки этапа:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (existing) {
return res.status(400).json({ error: 'Этап с таким номером уже существует' });
}
const query = `
UPDATE approval_stages
SET stage_number = ?, stage_name = ?, approver_role = ?,
approver_user_id = ?, is_required = ?, can_edit = ?,
can_comment = ?, deadline_days = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND document_type_id = ?
`;
db.run(query, [
stage_number, stage_name, approver_role || null,
approver_user_id || null, is_required ? 1 : 0,
can_edit ? 1 : 0, can_comment ? 1 : 0, deadline_days,
stageId, typeId
], function(err) {
if (err) {
console.error('Ошибка обновления этапа согласования:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Этап согласования не найден' });
}
res.json({ success: true, message: 'Этап согласования обновлен' });
});
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Удаление этапа согласования
router.delete('/admin/doc/types/:typeId/stages/:stageId', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const typeId = req.params.typeId;
const stageId = req.params.stageId;
db.run("DELETE FROM approval_stages WHERE id = ? AND document_type_id = ?",
[stageId, typeId], function(err) {
if (err) {
console.error('Ошибка удаления этапа согласования:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Этап согласования не найден' });
}
res.json({ success: true, message: 'Этап согласования удален' });
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Получение всех пользователей для выбора согласующих
router.get('/admin/doc/approvers', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const query = `
SELECT id, name, login, email, role
FROM users
WHERE role IN ('admin', 'teacher')
ORDER BY name
`;
db.all(query, [], (err, users) => {
if (err) {
console.error('Ошибка получения пользователей:', err);
return res.status(500).json({ error: 'Ошибка сервера' });
}
res.json(users);
});
} catch (error) {
console.error('Ошибка:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
// Получение статистики по документам
router.get('/admin/doc/stats', requireAdmin, async (req, res) => {
try {
const { getDb } = require('./database');
const db = getDb();
const stats = await new Promise((resolve, reject) => {
db.get(`
SELECT
COUNT(*) as total_documents,
COUNT(CASE WHEN status = 'draft' THEN 1 END) as draft_documents,
COUNT(CASE WHEN status = 'in_review' THEN 1 END) as in_review_documents,
COUNT(CASE WHEN status = 'approved' THEN 1 END) as approved_documents,
COUNT(CASE WHEN status = 'rejected' THEN 1 END) as rejected_documents,
COUNT(DISTINCT created_by) as unique_authors,
COUNT(DISTINCT document_type_id) as types_used
FROM documents
`, [], (err, row) => {
if (err) reject(err);
else resolve(row || {});
});
});
const recentDocuments = await new Promise((resolve, reject) => {
db.all(`
SELECT d.id, d.title, d.status, d.created_at,
u.name as author_name, dt.name as type_name
FROM documents d
LEFT JOIN users u ON d.created_by = u.id
LEFT JOIN document_types dt ON d.document_type_id = dt.id
ORDER BY d.created_at DESC
LIMIT 10
`, [], (err, rows) => {
if (err) reject(err);
else resolve(rows || []);
});
});
const typeStats = await new Promise((resolve, reject) => {
db.all(`
SELECT dt.name,
COUNT(d.id) as document_count,
COUNT(CASE WHEN d.status = 'approved' THEN 1 END) as approved_count,
COUNT(CASE WHEN d.status = 'rejected' THEN 1 END) as rejected_count
FROM document_types dt
LEFT JOIN documents d ON dt.id = d.document_type_id
GROUP BY dt.id
ORDER BY document_count DESC
`, [], (err, rows) => {
if (err) reject(err);
else resolve(rows || []);
});
});
res.json({
stats,
recentDocuments,
typeStats,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Ошибка получения статистики:', error);
res.status(500).json({ error: 'Ошибка получения статистики' });
}
});
module.exports = router;